add support to use PKCS#11 harware token to store certifice for e2ee
authorMatthieu Gallien <matthieu.gallien@nextcloud.com>
Fri, 28 Jul 2023 10:00:11 +0000 (12:00 +0200)
committerMatthieu Gallien <matthieu.gallien@nextcloud.com>
Fri, 7 Feb 2025 08:12:29 +0000 (09:12 +0100)
Close #5685

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
61 files changed:
CMakeLists.txt
NEXTCLOUD.cmake
config.h.in
resources.qrc
src/common/syncjournaldb.cpp
src/common/syncjournaldb.h
src/common/syncjournalfilerecord.h
src/csync/csync.h
src/gui/CMakeLists.txt
src/gui/EncryptionTokenSelectionWindow.qml [new file with mode: 0644]
src/gui/accountmanager.cpp
src/gui/accountsettings.cpp
src/gui/accountsettings.h
src/gui/connectionvalidator.cpp
src/gui/filedetails/sharemodel.cpp
src/gui/filedetails/sharemodel.h
src/gui/folder.cpp
src/gui/folderman.cpp
src/gui/folderstatusmodel.cpp
src/gui/owncloudgui.cpp
src/gui/socketapi/socketapi.cpp
src/gui/systray.cpp
src/gui/systray.h
src/gui/tray/EncryptionTokenDiscoveryDialog.qml [new file with mode: 0644]
src/gui/tray/activitydata.h
src/gui/tray/activitylistmodel.cpp
src/gui/tray/activitylistmodel.h
src/gui/tray/usermodel.cpp
src/libsync/CMakeLists.txt
src/libsync/account.cpp
src/libsync/account.h
src/libsync/clientsideencryption.cpp
src/libsync/clientsideencryption.h
src/libsync/clientsideencryptionjobs.cpp
src/libsync/clientsideencryptionjobs.h
src/libsync/clientsideencryptionprimitives.cpp
src/libsync/clientsideencryptionprimitives.h
src/libsync/clientsideencryptiontokenselector.cpp [new file with mode: 0644]
src/libsync/clientsideencryptiontokenselector.h [new file with mode: 0644]
src/libsync/discovery.cpp
src/libsync/discoveryphase.cpp
src/libsync/discoveryphase.h
src/libsync/encryptedfoldermetadatahandler.cpp
src/libsync/encryptfolderjob.cpp
src/libsync/foldermetadata.cpp
src/libsync/foldermetadata.h
src/libsync/owncloudpropagator.cpp
src/libsync/progressdispatcher.cpp
src/libsync/propagatedownload.cpp
src/libsync/propagatedownloadencrypted.cpp
src/libsync/propagateremotemkdir.cpp
src/libsync/propagateupload.cpp
src/libsync/propagateuploadencrypted.cpp
src/libsync/syncengine.cpp
src/libsync/syncfileitem.cpp
src/libsync/syncfileitem.h
src/libsync/updatee2eefoldermetadatajob.cpp
src/libsync/updatemigratede2eemetadatajob.cpp
src/libsync/updatemigratede2eemetadatajob.h
test/testclientsideencryptionv2.cpp
test/testsecurefiledrop.cpp

index 54fdf0a3be059955f04bb3abe657296378ab5a9b..5b7448b2e3625bc56dea5d6de1663c500eda1254 100644 (file)
@@ -233,20 +233,25 @@ if(BUILD_CLIENT)
     find_package(Sphinx)
     find_package(PdfLatex)
     find_package(OpenSSL 1.1 REQUIRED )
+    find_package(PkgConfig REQUIRED)
+    pkg_check_modules(OPENSC-LIBP11 libp11 REQUIRED IMPORTED_TARGET)
 
-   find_package(ZLIB REQUIRED)
-   find_package(SQLite3 3.9.0 REQUIRED)
+    set(ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH "" CACHE PATH "Path to the driver for end-to-end encryption token")
+    option(CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN "Enforce use of an hardware token for end-to-end encryption" false)
 
-   if(NOT WIN32 AND NOT APPLE)
-      find_package(PkgConfig REQUIRED)
-      pkg_check_modules(CLOUDPROVIDERS cloudproviders IMPORTED_TARGET)
+    find_package(ZLIB REQUIRED)
+    find_package(SQLite3 3.9.0 REQUIRED)
 
-      if(CLOUDPROVIDERS_FOUND)
-        pkg_check_modules(DBUS-1 REQUIRED dbus-1 IMPORTED_TARGET)
-        pkg_check_modules(GIO REQUIRED gio-2.0 IMPORTED_TARGET)
-        pkg_check_modules(GLIB2 REQUIRED glib-2.0 IMPORTED_TARGET)
-      endif()
-   endif()
+    if(NOT WIN32 AND NOT APPLE)
+        find_package(PkgConfig REQUIRED)
+        pkg_check_modules(CLOUDPROVIDERS cloudproviders IMPORTED_TARGET)
+
+        if(CLOUDPROVIDERS_FOUND)
+            pkg_check_modules(DBUS-1 REQUIRED dbus-1 IMPORTED_TARGET)
+            pkg_check_modules(GIO REQUIRED gio-2.0 IMPORTED_TARGET)
+            pkg_check_modules(GLIB2 REQUIRED glib-2.0 IMPORTED_TARGET)
+        endif()
+    endif()
 endif()
 
 option(BUILD_WITH_WEBENGINE "BUILD_WITH_WEBENGINE" ON)
index 85a46b38bea50bff81d9df08f6ca5ae67a5578a9..9b0129263b631f4d3457614a74ab3c5a03962c9f 100644 (file)
@@ -63,7 +63,6 @@ set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR ${NEXTCLOUD_BACKGROUND_COLOR} CA
 set( APPLICATION_WIZARD_HEADER_TITLE_COLOR "#ffffff" CACHE STRING "Hex color of the text in the wizard header")
 option( APPLICATION_WIZARD_USE_CUSTOM_LOGO "Use the logo from ':/client/theme/colored/wizard_logo.(png|svg)' else the default application icon is used" ON )
 
-
 #
 ## Windows Shell Extensions & MSI - IMPORTANT: Generate new GUIDs for custom builds with "guidgen" or "uuidgen"
 #
index faaec1bea23f6322b285692e8987627332ab30e5..bcf061028fb0d47b01cd245ae5a09bb6108a00c7 100644 (file)
@@ -66,4 +66,8 @@
 
 #cmakedefine WITH_WEBENGINE
 
+#cmakedefine01 CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN
+
+#cmakedefine ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH "@ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH@"
+
 #endif
index 19994e2962f1fe9d66de9e1a6b4172896845c800..715f7cf8174bc32025ce9064d375c6f8f3f469d5 100644 (file)
@@ -6,6 +6,7 @@
         <file>src/gui/UserStatusSelectorButton.qml</file>
         <file>src/gui/PredefinedStatusButton.qml</file>
         <file>src/gui/ErrorBox.qml</file>
+        <file>src/gui/EncryptionTokenSelectionWindow.qml</file>
         <file>src/gui/filedetails/FileActivityView.qml</file>
         <file>src/gui/filedetails/FileDetailsPage.qml</file>
         <file>src/gui/filedetails/FileDetailsView.qml</file>
@@ -45,6 +46,7 @@
         <file>src/gui/tray/TalkReplyTextField.qml</file>
         <file>src/gui/tray/CallNotificationDialog.qml</file>
         <file>src/gui/tray/EditFileLocallyLoadingDialog.qml</file>
+        <file>src/gui/tray/EncryptionTokenDiscoveryDialog.qml</file>
         <file>src/gui/tray/NCBusyIndicator.qml</file>
         <file>src/gui/tray/NCIconWithBackgroundImage.qml</file>
         <file>src/gui/tray/NCToolTip.qml</file>
index fffd846b547a16a47045aedeb2688ec2bdd93097..927e2dcc1affbf5b1ec11bdc4923f746a549a82b 100644 (file)
@@ -48,8 +48,9 @@ Q_LOGGING_CATEGORY(lcDb, "nextcloud.sync.database", QtInfoMsg)
 
 #define GET_FILE_RECORD_QUERY \
         "SELECT path, inode, modtime, type, md5, fileid, remotePerm, filesize," \
-        "  ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted, " \
-        "  lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, lockToken, isShared, lastShareStateFetchedTimestmap, sharedByMe, isLivePhoto, livePhotoFile" \
+        "  ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted, e2eCertificateFingerprint, " \
+        "  lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, lockToken, isShared, lastShareStateFetchedTimestmap, " \
+        "  sharedByMe, isLivePhoto, livePhotoFile" \
         " FROM metadata" \
         "  LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id"
 
@@ -67,19 +68,20 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que
     rec._checksumHeader = query.baValue(9);
     rec._e2eMangledName = query.baValue(10);
     rec._e2eEncryptionStatus = static_cast<SyncJournalFileRecord::EncryptionStatus>(query.intValue(11));
-    rec._lockstate._locked = query.intValue(12) > 0;
-    rec._lockstate._lockOwnerDisplayName = query.stringValue(13);
-    rec._lockstate._lockOwnerId = query.stringValue(14);
-    rec._lockstate._lockOwnerType = query.int64Value(15);
-    rec._lockstate._lockEditorApp = query.stringValue(16);
-    rec._lockstate._lockTime = query.int64Value(17);
-    rec._lockstate._lockTimeout = query.int64Value(18);
-    rec._lockstate._lockToken = query.stringValue(19);
-    rec._isShared = query.intValue(20) > 0;
-    rec._lastShareStateFetchedTimestamp = query.int64Value(21);
-    rec._sharedByMe = query.intValue(22) > 0;
-    rec._isLivePhoto = query.intValue(23) > 0;
-    rec._livePhotoFile = query.stringValue(24);
+    rec._e2eCertificateFingerprint = query.baValue(12);
+    rec._lockstate._locked = query.intValue(13) > 0;
+    rec._lockstate._lockOwnerDisplayName = query.stringValue(14);
+    rec._lockstate._lockOwnerId = query.stringValue(15);
+    rec._lockstate._lockOwnerType = query.int64Value(16);
+    rec._lockstate._lockEditorApp = query.stringValue(17);
+    rec._lockstate._lockTime = query.int64Value(18);
+    rec._lockstate._lockTimeout = query.int64Value(19);
+    rec._lockstate._lockToken = query.stringValue(20);
+    rec._isShared = query.intValue(21) > 0;
+    rec._lastShareStateFetchedTimestamp = query.int64Value(22);
+    rec._sharedByMe = query.intValue(23) > 0;
+    rec._isLivePhoto = query.intValue(24) > 0;
+    rec._livePhotoFile = query.stringValue(25);
 }
 
 static QByteArray defaultJournalMode(const QString &dbPath)
@@ -783,6 +785,7 @@ bool SyncJournalDb::updateMetadataTableStructure()
     addColumn(QStringLiteral("contentChecksumTypeId"), QStringLiteral("INTEGER"));
     addColumn(QStringLiteral("e2eMangledName"), QStringLiteral("TEXT"));
     addColumn(QStringLiteral("isE2eEncrypted"), QStringLiteral("INTEGER"));
+    addColumn(QStringLiteral("e2eCertificateFingerprint"), QStringLiteral("TEXT"));
     addColumn(QStringLiteral("isShared"), QStringLiteral("INTEGER"));
     addColumn(QStringLiteral("lastShareStateFetchedTimestmap"), QStringLiteral("INTEGER"));
     addColumn(QStringLiteral("sharedByMe"), QStringLiteral("INTEGER"));
@@ -995,9 +998,9 @@ Result<void, QString> SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
 
     const auto query = _queryManager.get(PreparedSqlQueryManager::SetFileRecordQuery, QByteArrayLiteral("INSERT OR REPLACE INTO metadata "
                                                                                                         "(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, "
-                                                                                                        "contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted, lock, lockType, lockOwnerDisplayName, lockOwnerId, "
+                                                                                                        "contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted, e2eCertificateFingerprint, lock, lockType, lockOwnerDisplayName, lockOwnerId, "
                                                                                                         "lockOwnerEditor, lockTime, lockTimeout, lockToken, isShared, lastShareStateFetchedTimestmap, sharedByMe, isLivePhoto, livePhotoFile) "
-                                                                                                        "VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7,  ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29, ?30, ?31);"),
+                                                                                                        "VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7,  ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29, ?30, ?31, ?32);"),
         _db);
     if (!query) {
         qCDebug(lcDb) << "database error:" << query->error();
@@ -1022,19 +1025,20 @@ Result<void, QString> SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
     query->bindValue(16, contentChecksumTypeId);
     query->bindValue(17, record._e2eMangledName);
     query->bindValue(18, static_cast<int>(record._e2eEncryptionStatus));
-    query->bindValue(19, record._lockstate._locked ? 1 : 0);
-    query->bindValue(20, record._lockstate._lockOwnerType);
-    query->bindValue(21, record._lockstate._lockOwnerDisplayName);
-    query->bindValue(22, record._lockstate._lockOwnerId);
-    query->bindValue(23, record._lockstate._lockEditorApp);
-    query->bindValue(24, record._lockstate._lockTime);
-    query->bindValue(25, record._lockstate._lockTimeout);
-    query->bindValue(26, record._lockstate._lockToken);
-    query->bindValue(27, record._isShared);
-    query->bindValue(28, record._lastShareStateFetchedTimestamp);
-    query->bindValue(29, record._sharedByMe);
-    query->bindValue(30, record._isLivePhoto);
-    query->bindValue(31, record._livePhotoFile);
+    query->bindValue(19, record._e2eCertificateFingerprint);
+    query->bindValue(20, record._lockstate._locked ? 1 : 0);
+    query->bindValue(21, record._lockstate._lockOwnerType);
+    query->bindValue(22, record._lockstate._lockOwnerDisplayName);
+    query->bindValue(23, record._lockstate._lockOwnerId);
+    query->bindValue(24, record._lockstate._lockEditorApp);
+    query->bindValue(25, record._lockstate._lockTime);
+    query->bindValue(26, record._lockstate._lockTimeout);
+    query->bindValue(27, record._lockstate._lockToken);
+    query->bindValue(28, record._isShared);
+    query->bindValue(29, record._lastShareStateFetchedTimestamp);
+    query->bindValue(30, record._sharedByMe);
+    query->bindValue(31, record._isLivePhoto);
+    query->bindValue(32, record._livePhotoFile);
 
     if (!query->exec()) {
         qCDebug(lcDb) << "database error:" << query->error();
@@ -3035,7 +3039,7 @@ SyncJournalDb::PinStateInterface::rawList()
 
 SyncJournalDb::PinStateInterface SyncJournalDb::internalPinStates()
 {
-    return {this};
+    return PinStateInterface{this};
 }
 
 void SyncJournalDb::commit(const QString &context, bool startTrans)
index ae4824a41766cfd9bba4f7b33f21394e9b33c415..a74f4f7f1f83adc6f59950e6661f3ff8af80539e 100644 (file)
@@ -304,6 +304,11 @@ public:
      */
     struct OCSYNC_EXPORT PinStateInterface
     {
+        explicit PinStateInterface(SyncJournalDb *db)
+            : _db(db)
+        {
+        }
+
         PinStateInterface(const PinStateInterface &) = delete;
         PinStateInterface(PinStateInterface &&) = delete;
 
index 4d299e3a9ff8d25633dee4d554b3a6e95029c5a2..f36b53095b300619f1fb9f7dff79e234021cf612 100644 (file)
@@ -84,6 +84,7 @@ public:
     QByteArray _checksumHeader;
     QByteArray _e2eMangledName;
     EncryptionStatus _e2eEncryptionStatus = EncryptionStatus::NotEncrypted;
+    QByteArray _e2eCertificateFingerprint;
     SyncJournalFileLockInfo _lockstate;
     bool _isShared = false;
     qint64 _lastShareStateFetchedTimestamp = 0;
index 9da7497f75c7f0bbddeff644b155499a749985bd..8329020f5846321621d6c799ac647f1bcdc7aeba 100644 (file)
@@ -140,24 +140,25 @@ Q_ENUM_NS(csync_status_codes_e)
   * the csync state of a file.
   */
 enum SyncInstructions {
-    CSYNC_INSTRUCTION_NONE                = 0,       /* Nothing to do (UPDATE|RECONCILE) */
-    CSYNC_INSTRUCTION_EVAL                = 1 << 0,  /* There was changed compared to the DB (UPDATE) */
-    CSYNC_INSTRUCTION_REMOVE              = 1 << 1,  /* The file need to be removed (RECONCILE) */
-    CSYNC_INSTRUCTION_RENAME              = 1 << 2,  /* The file need to be renamed (RECONCILE) */
-    CSYNC_INSTRUCTION_EVAL_RENAME         = 1 << 11, /* The file is new, it is the destination of a rename (UPDATE) */
-    CSYNC_INSTRUCTION_NEW                 = 1 << 3,  /* The file is new compared to the db (UPDATE) */
-    CSYNC_INSTRUCTION_CONFLICT            = 1 << 4,  /* The file need to be downloaded because it is a conflict (RECONCILE) */
-    CSYNC_INSTRUCTION_IGNORE              = 1 << 5,  /* The file is ignored (UPDATE|RECONCILE) */
-    CSYNC_INSTRUCTION_SYNC                = 1 << 6,  /* The file need to be pushed to the other remote (RECONCILE) */
-    CSYNC_INSTRUCTION_STAT_ERROR          = 1 << 7,
-    CSYNC_INSTRUCTION_ERROR               = 1 << 8,
-    CSYNC_INSTRUCTION_TYPE_CHANGE         = 1 << 9,  /* Like NEW, but deletes the old entity first (RECONCILE)
-                                                        Used when the type of something changes from directory to file
-                                                        or back. */
-    CSYNC_INSTRUCTION_UPDATE_METADATA     = 1 << 10, /* If the etag has been updated and need to be writen to the db,
-                                                        but without any propagation (UPDATE|RECONCILE) */
-    CSYNC_INSTRUCTION_CASE_CLASH_CONFLICT = 1 << 12, /* The file need to be downloaded because it is a case clash conflict (RECONCILE) */
-    CSYNC_INSTRUCTION_UPDATE_VFS_METADATA = 1 << 13, /* vfs item metadata are out of sync and we need to tell operating system about it */
+    CSYNC_INSTRUCTION_NONE                       = 0,       /* Nothing to do (UPDATE|RECONCILE) */
+    CSYNC_INSTRUCTION_EVAL                       = 1 << 0,  /* There was changed compared to the DB (UPDATE) */
+    CSYNC_INSTRUCTION_REMOVE                     = 1 << 1,  /* The file need to be removed (RECONCILE) */
+    CSYNC_INSTRUCTION_RENAME                     = 1 << 2,  /* The file need to be renamed (RECONCILE) */
+    CSYNC_INSTRUCTION_EVAL_RENAME                = 1 << 11, /* The file is new, it is the destination of a rename (UPDATE) */
+    CSYNC_INSTRUCTION_NEW                        = 1 << 3,  /* The file is new compared to the db (UPDATE) */
+    CSYNC_INSTRUCTION_CONFLICT                   = 1 << 4,  /* The file need to be downloaded because it is a conflict (RECONCILE) */
+    CSYNC_INSTRUCTION_IGNORE                     = 1 << 5,  /* The file is ignored (UPDATE|RECONCILE) */
+    CSYNC_INSTRUCTION_SYNC                       = 1 << 6,  /* The file need to be pushed to the other remote (RECONCILE) */
+    CSYNC_INSTRUCTION_STAT_ERROR                 = 1 << 7,
+    CSYNC_INSTRUCTION_ERROR                      = 1 << 8,
+    CSYNC_INSTRUCTION_TYPE_CHANGE                = 1 << 9,  /* Like NEW, but deletes the old entity first (RECONCILE)
+                                                               Used when the type of something changes from directory to file
+                                                               or back. */
+    CSYNC_INSTRUCTION_UPDATE_METADATA            = 1 << 10, /* If the etag has been updated and need to be writen to the db,
+                                                               but without any propagation (UPDATE|RECONCILE) */
+    CSYNC_INSTRUCTION_CASE_CLASH_CONFLICT        = 1 << 12, /* The file need to be downloaded because it is a case clash conflict (RECONCILE) */
+    CSYNC_INSTRUCTION_UPDATE_VFS_METADATA        = 1 << 13, /* vfs item metadata are out of sync and we need to tell operating system about it */
+    CSYNC_INSTRUCTION_UPDATE_ENCRYPTION_METADATA = 1 << 14, /* encryption metadata needs update after certificate was migrated */
 };
 
 Q_ENUM_NS(SyncInstructions)
index 014cfce4ca2fccf3468f73e4f5e682cd42195e38..4bd77fabad29464389f045a7e41a1472d43b6394 100644 (file)
@@ -146,6 +146,7 @@ set(client_SRCS
     syncrunfilelog.cpp
     systray.h
     systray.cpp
+    EncryptionTokenSelectionWindow.qml
     thumbnailjob.h
     thumbnailjob.cpp
     userinfo.h
diff --git a/src/gui/EncryptionTokenSelectionWindow.qml b/src/gui/EncryptionTokenSelectionWindow.qml
new file mode 100644 (file)
index 0000000..8872d97
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2023 by Matthieu Gallien <matthieu.gallien@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+import QtQml.Models 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+import "./tray"
+
+ApplicationWindow {
+    id: encryptionKeyChooserDialog
+
+    required property var certificatesInfo
+    required property ClientSideEncryptionTokenSelector certificateSelector
+    property string selectedSerialNumber: ''
+
+    flags: Qt.Window | Qt.Dialog
+    visible: true
+    modality: Qt.ApplicationModal
+
+    width: 400
+    height: 600
+    minimumWidth: 400
+    minimumHeight: 600
+
+    title: qsTr('Token Encryption Key Chooser')
+
+    // TODO: Rather than setting all these palette colours manually,
+    // create a custom style and do it for all components globally
+    palette {
+        text: Style.ncTextColor
+        windowText: Style.ncTextColor
+        buttonText: Style.ncTextColor
+        brightText: Style.ncTextBrightColor
+        highlight: Style.lightHover
+        highlightedText: Style.ncTextColor
+        light: Style.lightHover
+        midlight: Style.ncSecondaryTextColor
+        mid: Style.darkerHover
+        dark: Style.menuBorder
+        button: Style.buttonBackgroundColor
+        window: Style.backgroundColor
+        base: Style.backgroundColor
+        toolTipBase: Style.backgroundColor
+        toolTipText: Style.ncTextColor
+    }
+
+    onClosing: function(close) {
+        Systray.destroyDialog(self);
+        close.accepted = true
+    }
+
+    ColumnLayout {
+        anchors.fill: parent
+        anchors.leftMargin: 20
+        anchors.rightMargin: 20
+        anchors.bottomMargin: 20
+        anchors.topMargin: 20
+        spacing: 15
+        z: 2
+
+        EnforcedPlainTextLabel {
+            text: qsTr("Available Keys for end-to-end Encryption:")
+            font.bold: true
+            font.pixelSize: Style.bigFontPixelSizeResolveConflictsDialog
+            Layout.fillWidth: true
+        }
+
+        ScrollView {
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+
+            clip: true
+
+            ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+
+            ListView {
+                id: tokensListView
+
+                currentIndex: -1
+
+                model: DelegateModel {
+                    model: certificatesInfo
+
+                    delegate: ItemDelegate {
+                        width: tokensListView.contentItem.width
+
+                        text: modelData.subject
+
+                        highlighted: tokensListView.currentIndex === index
+
+                        onClicked: function()
+                        {
+                            tokensListView.currentIndex = index
+                            selectedSerialNumber = modelData.serialNumber
+                        }
+                    }
+                }
+            }
+        }
+
+        DialogButtonBox {
+            Layout.fillWidth: true
+
+            Button {
+                text: qsTr("Choose")
+                DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
+            }
+            Button {
+                text: qsTr("Cancel")
+                DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
+            }
+
+            onAccepted: function() {
+                Systray.destroyDialog(encryptionKeyChooserDialog)
+                certificateSelector.serialNumber = selectedSerialNumber
+            }
+
+            onRejected: function() {
+                Systray.destroyDialog(encryptionKeyChooserDialog)
+                certificateSelector.serialNumber = ''
+            }
+        }
+    }
+
+    Rectangle {
+        color: Style.backgroundColor
+        anchors.fill: parent
+        z: 1
+    }
+}
index ccfcfec4cbd0f1b243c8d0429f4783b8da265a94..36f6110821bd5d9b4a8b603dda0ca8bfb934c070 100644 (file)
@@ -59,6 +59,7 @@ constexpr auto networkUploadLimitSettingC = "networkUploadLimitSetting";
 constexpr auto networkDownloadLimitSettingC = "networkDownloadLimitSetting";
 constexpr auto networkUploadLimitC = "networkUploadLimit";
 constexpr auto networkDownloadLimitC = "networkDownloadLimit";
+constexpr auto encryptionCertificateSha256FingerprintC = "encryptionCertificateSha256Fingerprint";
 constexpr auto generalC = "General";
 
 constexpr auto dummyAuthTypeC = "dummy";
@@ -350,7 +351,7 @@ void AccountManager::saveAccountHelper(Account *account, QSettings &settings, bo
     settings.setValue(QLatin1String(serverColorC), account->_serverColor);
     settings.setValue(QLatin1String(serverTextColorC), account->_serverTextColor);
     settings.setValue(QLatin1String(serverHasValidSubscriptionC), account->serverHasValidSubscription());
-
+    settings.setValue(QLatin1String(encryptionCertificateSha256FingerprintC), account->encryptionCertificateFingerprint());
     if (!account->_skipE2eeMetadataChecksumValidation) {
         settings.remove(QLatin1String(skipE2eeMetadataChecksumValidationC));
     } else {
@@ -556,6 +557,8 @@ AccountPtr AccountManager::loadAccountHelper(QSettings &settings)
     });
     job->start();
 
+    acc->setEncryptionCertificateFingerprint(settings.value(QLatin1String(encryptionCertificateSha256FingerprintC)).toByteArray());
+
     // now the server cert, it is in the general group
     settings.beginGroup(QLatin1String(generalC));
     const auto certs = QSslCertificate::fromData(settings.value(caCertsKeyC).toByteArray());
index 6af3208d0269f02f08199c73515b83d0d1cb6990..579b053a0dcaa5ab3d87aee353dab126cdcf550d 100644 (file)
@@ -74,6 +74,7 @@ constexpr auto e2eUiActionIdKey = "id";
 constexpr auto e2EeUiActionEnableEncryptionId = "enable_encryption";
 constexpr auto e2EeUiActionDisableEncryptionId = "disable_encryption";
 constexpr auto e2EeUiActionDisplayMnemonicId = "display_mnemonic";
+constexpr auto e2EeUiActionMigrateCertificateId = "migrate_certificate";
 }
 
 namespace OCC {
@@ -292,6 +293,11 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
         this, &AccountSettings::slotUpdateQuota);
 
     customizeStyle();
+
+    connect(_accountState->account()->e2e(), &ClientSideEncryption::startingDiscoveryEncryptionUsbToken,
+            Systray::instance(), &Systray::createEncryptionTokenDiscoveryDialog);
+    connect(_accountState->account()->e2e(), &ClientSideEncryption::finishedDiscoveryEncryptionUsbToken,
+            Systray::instance(), &Systray::destroyEncryptionTokenDiscoveryDialog);
 }
 
 void AccountSettings::slotE2eEncryptionMnemonicReady()
@@ -301,10 +307,16 @@ void AccountSettings::slotE2eEncryptionMnemonicReady()
         disableEncryptionForAccount(_accountState->account());
     });
 
-    const auto actionDisplayMnemonic = addActionToEncryptionMessage(tr("Display mnemonic"), e2EeUiActionDisplayMnemonicId);
-    connect(actionDisplayMnemonic, &QAction::triggered, this, [this]() {
-        displayMnemonic(_accountState->account()->e2e()->_mnemonic);
-    });
+    if (_accountState->account()->e2e()->userCertificateNeedsMigration()) {
+        slotE2eEncryptionCertificateNeedMigration();
+    }
+
+    if (!_accountState->account()->e2e()->getMnemonic().isEmpty()) {
+        const auto actionDisplayMnemonic = addActionToEncryptionMessage(tr("Display mnemonic"), e2EeUiActionDisplayMnemonicId);
+        connect(actionDisplayMnemonic, &QAction::triggered, this, [this]() {
+            displayMnemonic(_accountState->account()->e2e()->getMnemonic());
+        });
+    }
 
     _ui->encryptionMessage->setMessageType(KMessageWidget::Positive);
     _ui->encryptionMessage->setText(tr("End-to-end encryption has been enabled for this account"));
@@ -317,18 +329,19 @@ void AccountSettings::slotE2eEncryptionGenerateKeys()
     connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotE2eEncryptionInitializationFinished);
     _accountState->account()->setE2eEncryptionKeysGenerationAllowed(true);
     _accountState->account()->setAskUserForMnemonic(true);
-    _accountState->account()->e2e()->initialize(_accountState->account());
+    _accountState->account()->e2e()->initialize(this, _accountState->account());
 }
 
 void AccountSettings::slotE2eEncryptionInitializationFinished(bool isNewMnemonicGenerated)
 {
     disconnect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotE2eEncryptionInitializationFinished);
-    if (!_accountState->account()->e2e()->_mnemonic.isEmpty()) {
+    if (_accountState->account()->e2e()->isInitialized()) {
         removeActionFromEncryptionMessage(e2EeUiActionEnableEncryptionId);
         slotE2eEncryptionMnemonicReady();
         if (isNewMnemonicGenerated) {
-            displayMnemonic(_accountState->account()->e2e()->_mnemonic);
+            displayMnemonic(_accountState->account()->e2e()->getMnemonic());
         }
+        Q_EMIT _accountState->account()->wantsFoldersSynced();
     }
     _accountState->account()->setAskUserForMnemonic(false);
 }
@@ -398,7 +411,7 @@ bool AccountSettings::canEncryptOrDecrypt(const FolderStatusModel::SubFolderInfo
         return false;
     }
 
-    if (!_accountState->account()->e2e() || _accountState->account()->e2e()->_mnemonic.isEmpty()) {
+    if (!_accountState->account()->e2e() || !_accountState->account()->e2e()->isInitialized()) {
         QMessageBox msgBox;
         msgBox.setText(tr("End-to-end encryption is not configured on this device. "
                           "Once it is configured, you will be able to encrypt this folder.\n"
@@ -1125,6 +1138,16 @@ void AccountSettings::disableEncryptionForAccount(const AccountPtr &account) con
     }
 }
 
+void AccountSettings::migrateCertificateForAccount(const AccountPtr &account)
+{
+    for (const auto action : _ui->encryptionMessage->actions()) {
+        _ui->encryptionMessage->removeAction(action);
+    }
+
+    account->e2e()->migrateCertificate();
+    slotE2eEncryptionGenerateKeys();
+}
+
 void AccountSettings::showConnectionLabel(const QString &message, QStringList errors)
 {
     const auto errStyle = QLatin1String("color:#ffffff; background-color:#bb4d4d;padding:5px;"
@@ -1472,7 +1495,7 @@ void AccountSettings::slotSelectiveSyncChanged(const QModelIndex &topLeft,
 
 void AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync()
 {
-    if (_accountState->account()->e2e()->_mnemonic.isEmpty()) {
+    if (!_accountState->account()->e2e()->isInitialized()) {
         return;
     }
 
@@ -1505,6 +1528,14 @@ void AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync()
     }
 }
 
+void AccountSettings::slotE2eEncryptionCertificateNeedMigration()
+{
+    const auto actionMigrateCertificate = addActionToEncryptionMessage(tr("Migrate certificate to a new one"), e2EeUiActionMigrateCertificateId);
+    connect(actionMigrateCertificate, &QAction::triggered, this, [this] {
+        migrateCertificateForAccount(_accountState->account());
+    });
+}
+
 void AccountSettings::updateBlackListAndScheduleFolderSync(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist) const
 {
     folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList);
@@ -1650,13 +1681,13 @@ void AccountSettings::initializeE2eEncryption()
 {
     connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync);
 
-    if (!_accountState->account()->e2e()->_mnemonic.isEmpty()) {
+    if (_accountState->account()->e2e()->isInitialized()) {
         slotE2eEncryptionMnemonicReady();
     } else {
         initializeE2eEncryptionSettingsMessage();
 
         connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, [this] {
-            if (!_accountState->account()->e2e()->_publicKey.isNull()) {
+            if (!_accountState->account()->e2e()->getPublicKey().isNull()) {
                 _ui->encryptionMessage->setText(tr("End-to-end encryption has been enabled on this account with another device."
                                                    "<br>"
                                                    "It can be enabled on this device by entering your mnemonic."
@@ -1665,7 +1696,7 @@ void AccountSettings::initializeE2eEncryption()
             }
         });
         _accountState->account()->setE2eEncryptionKeysGenerationAllowed(false);
-        _accountState->account()->e2e()->initialize(_accountState->account());
+        _accountState->account()->e2e()->initialize(this, _accountState->account());
     }
 }
 
@@ -1680,7 +1711,7 @@ void AccountSettings::resetE2eEncryption()
     checkClientSideEncryptionState();
 
     const auto account = _accountState->account();
-    if (account->e2e()->_mnemonic.isEmpty()) {
+    if (!account->e2e()->isInitialized()) {
         FolderMan::instance()->removeE2eFiles(account);
     }
 }
index 8981f1dffe6f81479408b3fee72ce30269c421ad..592e7175e1d5c9875767761c36fafe431992fdd7 100644 (file)
@@ -62,6 +62,7 @@ public:
     ~AccountSettings() override;
     [[nodiscard]] QSize sizeHint() const override { return ownCloudGui::settingsDialogSize(); }
     bool canEncryptOrDecrypt(const FolderStatusModel::SubFolderInfo* folderInfo);
+    [[nodiscard]] OCC::AccountState *accountsState() const { return _accountState; }
 
 signals:
     void folderChanged();
@@ -76,7 +77,6 @@ public slots:
     void slotUpdateQuota(qint64 total, qint64 used);
     void slotAccountStateChanged();
     void slotStyleChanged();
-    OCC::AccountState *accountsState() { return _accountState; }
     void slotHideSelectiveSyncWidget();
 
 protected slots:
@@ -116,6 +116,8 @@ protected slots:
                                   const QVector<int> &roles);
     void slotPossiblyUnblacklistE2EeFoldersAndRestartSync();
 
+    void slotE2eEncryptionCertificateNeedMigration();
+
 private slots:
     void updateBlackListAndScheduleFolderSync(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist) const;
     void folderTerminateSyncAndUpdateBlackList(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist);
@@ -123,6 +125,7 @@ private slots:
 private slots:
     void displayMnemonic(const QString &mnemonic);
     void disableEncryptionForAccount(const OCC::AccountPtr &account) const;
+    void migrateCertificateForAccount(const OCC::AccountPtr &account);
     void showConnectionLabel(const QString &message, QStringList errors = QStringList());
     void openIgnoredFilesDialog(const QString & absFolderPath);
     void customizeStyle();
index 75cb21762fcb053b3cb23a6e98f1bfca2e43305f..ebe1c1af525f6f0b6ee8e6d180cb372350fab23c 100644 (file)
@@ -321,7 +321,7 @@ void ConnectionValidator::slotUserFetched(UserInfo *userInfo)
 
 #ifndef TOKEN_AUTH_ONLY
     connect(_account->e2e(), &ClientSideEncryption::initializationFinished, this, &ConnectionValidator::reportConnected);
-    _account->e2e()->initialize(_account);
+    _account->e2e()->initialize(nullptr, _account);
 #else
     reportResult(Connected);
 #endif
index 3e0662dc220ad6b95e59a5de243bd3c05465d301..b7336c0c8ab957290d2262d04d0fde58e928cf08 100644 (file)
@@ -369,8 +369,12 @@ void ShareModel::initShareManager()
         connect(_manager.data(), &ShareManager::linkShareCreated, this, &ShareModel::slotAddShare);
         connect(_manager.data(), &ShareManager::linkShareRequiresPassword, this, &ShareModel::requestPasswordForLinkShare);
         connect(_manager.data(), &ShareManager::serverError, this, [this](const int code, const QString &message) {
-            _hasInitialShareFetchCompleted = true;
-            Q_EMIT hasInitialShareFetchCompletedChanged();
+            if (!_hasInitialShareFetchCompleted) {
+                _hasInitialShareFetchCompleted = true;
+                Q_EMIT hasInitialShareFetchCompletedChanged();
+            }
+
+            qCWarning(lcShareModel) << "Error from server from ShareManager class and initShareManager" << code << message;
             emit serverError(code, message);
         });
 
@@ -633,7 +637,10 @@ void ShareModel::slotAddShare(const SharePtr &share)
     const QPersistentModelIndex sharePersistentIndex(shareModelIndex);
     _shareIdIndexHash.insert(shareId, sharePersistentIndex);
 
-    connect(share.data(), &Share::serverError, this, &ShareModel::slotServerError);
+    connect(share.data(), &Share::serverError, this, [this] (int code, const QString &message) {
+        qCWarning(lcShareModel) << "Error from server from Share class" << code << message;
+        Q_EMIT serverError(code, message);
+    });
     connect(share.data(), &Share::passwordSetError, this, [this, shareId](const int code, const QString &message) {
         _shareIdRecentlySetPasswords.remove(shareId);
         slotSharePasswordSet(shareId);
@@ -656,10 +663,6 @@ void ShareModel::slotAddShare(const SharePtr &share)
         connect(userGroupShare.data(), &UserGroupShare::expireDateSet, this, [this, shareId]{ slotShareExpireDateSet(shareId); });
     }
 
-    if (_manager) {
-        connect(_manager.data(), &ShareManager::serverError, this, &ShareModel::slotServerError);
-    }
-
     handleLinkShare();
     Q_EMIT sharesChanged();
 }
@@ -708,12 +711,6 @@ void ShareModel::slotRemoveShareWithId(const QString &shareId)
     Q_EMIT sharesChanged();
 }
 
-void ShareModel::slotServerError(const int code, const QString &message)
-{
-    qCWarning(lcShareModel) << "Error from server" << code << message;
-    Q_EMIT serverError(code, message);
-}
-
 void ShareModel::slotAddSharee(const ShareePtr &sharee)
 {
     if(!sharee) {
index 5357a9366d6f37e6f2c4853725a8c73ea0c29733..e34dece4b1f9deb0dbe6dc18f0e59332dd2a45f2 100644 (file)
@@ -216,7 +216,6 @@ private slots:
     void setHideDownloadEnabledChangeInProgress(const QString &shareId, const bool isInProgress);
 
     void slotPropfindReceived(const QVariantMap &result);
-    void slotServerError(const int code, const QString &message);
     void slotAddShare(const OCC::SharePtr &share);
     void slotRemoveShareWithId(const QString &shareId);
     void slotSharesFetched(const QList<OCC::SharePtr> &shares);
index d68a2196367f9cf787b11be40521f06d09641fc2..126a39d5cf97ae844dac1392c74483363c1f25b8 100644 (file)
@@ -138,6 +138,11 @@ Folder::Folder(const FolderDefinition &definition,
 
     connect(_accountState->account().data(), &Account::capabilitiesChanged, this, &Folder::slotCapabilitiesChanged);
 
+    connect(_accountState->account().data(), &Account::wantsFoldersSynced, this, [this] () {
+        _engine->setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::FilesystemOnly);
+        QMetaObject::invokeMethod(_engine.data(), "startSync", Qt::QueuedConnection);
+    });
+
     // Potentially upgrade suffix vfs to windows vfs
     ENFORCE(_vfs);
     if (_definition.virtualFilesMode == Vfs::WithSuffix
index 317b67195009f9a8d25663542a0d52897cec3e3f..ff84383c526958493d7fa087adfde5af6f43b140 100644 (file)
@@ -674,7 +674,7 @@ void FolderMan::forceSyncForFolder(Folder *folder)
 
 void FolderMan::removeE2eFiles(const AccountPtr &account) const
 {
-    Q_ASSERT(account->e2e()->_mnemonic.isEmpty());
+    Q_ASSERT(!account->e2e()->isInitialized());
     for (const auto folder : map()) {
         if(folder->accountState()->account()->id() == account->id()) {
             folder->removeLocalE2eFiles();
index 87c1f9d829375f94e595dd0d1bb20870093b06bf..d67e6e747521e4e7334ab865246be381118942c4 100644 (file)
@@ -765,8 +765,7 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
 
         newInfo._isNonDecryptable = newInfo.isEncrypted()
             && _accountState->account()->e2e()
-            && !_accountState->account()->e2e()->_publicKey.isNull()
-            && _accountState->account()->e2e()->_privateKey.isNull();
+            && !_accountState->account()->e2e()->isInitialized();
 
         SyncJournalFileRecord rec;
         if (!parentInfo->_folder->journalDb()->getFileRecordByE2eMangledName(removeTrailingSlash(relativePath), &rec)) {
index 53b97c11331d83a7fdbf811daaccb2120f26e3ee..9a107f5eb75883393e289c772c52d58eda231919 100644 (file)
@@ -149,6 +149,7 @@ ownCloudGui::ownCloudGui(Application *parent)
     qmlRegisterUncreatableType<UnifiedSearchResultsListModel>("com.nextcloud.desktopclient", 1, 0, "UnifiedSearchResultsListModel", "UnifiedSearchResultsListModel");
     qmlRegisterUncreatableType<UserStatus>("com.nextcloud.desktopclient", 1, 0, "UserStatus", "Access to Status enum");
     qmlRegisterUncreatableType<Sharee>("com.nextcloud.desktopclient", 1, 0, "Sharee", "Access to Type enum");
+    qmlRegisterUncreatableType<ClientSideEncryptionTokenSelector>("com.nextcloud.desktopclient", 1, 0, "ClientSideEncryptionTokenSelector", "Access to the certificate selector");
 
     qRegisterMetaType<ActivityListModel *>("ActivityListModel*");
     qRegisterMetaType<UnifiedSearchResultsListModel *>("UnifiedSearchResultsListModel*");
@@ -606,10 +607,15 @@ void ownCloudGui::slotShowSettings()
     if (_settingsDialog.isNull()) {
         _settingsDialog = new SettingsDialog(this);
         _settingsDialog->setAttribute(Qt::WA_DeleteOnClose, true);
+
 #ifdef Q_OS_MAC
         auto *fgbg = new ForegroundBackground();
         _settingsDialog->installEventFilter(fgbg);
 #endif
+
+        connect(_tray.data(), &Systray::hideSettingsDialog,
+                _settingsDialog.data(), &SettingsDialog::close);
+
         _settingsDialog->show();
     }
     raiseDialog(_settingsDialog.data());
index 37dab4b2b3734a6cd1747cf51893dc77b0f24f28..c000ca8e20579186dd7a8466742b38a793e10088 100644 (file)
@@ -545,14 +545,14 @@ void SocketApi::processEncryptRequest(const QString &localFile)
     const auto rec = fileData.journalRecord();
     Q_ASSERT(rec.isValid());
 
-    if (!account->e2e() || account->e2e()->_mnemonic.isEmpty()) {
+    if (!account->e2e() || !account->e2e()->isInitialized()) {
         const int ret = QMessageBox::critical(
             nullptr,
             tr("Failed to encrypt folder at \"%1\"").arg(fileData.folderRelativePath),
             tr("The account %1 does not have end-to-end encryption configured. "
                "Please configure this in your account settings to enable folder encryption.").arg(account->prettyName()),
             QMessageBox::Ok
-        );
+            );
         Q_UNUSED(ret)
         return;
     }
index 8e45abe66b42be26caeb51a9f25707d7c386312a..5deb98a58b68038f80054f623c126d4194b115f6 100644 (file)
@@ -24,6 +24,7 @@
 #include "configfile.h"
 #include "accessmanager.h"
 #include "callstatechecker.h"
+#include "clientsideencryptiontokenselector.h"
 
 #include <QCursor>
 #include <QGuiApplication>
@@ -34,6 +35,8 @@
 #include <QScreen>
 #include <QMenu>
 #include <QGuiApplication>
+#include <QQuickView>
+#include <QMessageBox>
 
 #ifdef USE_FDO_NOTIFICATIONS
 #include <QDBusConnection>
@@ -316,6 +319,34 @@ void Systray::createResolveConflictsDialog(const OCC::ActivityList &allConflicts
     dialogWindow->requestActivate();
 }
 
+void Systray::createEncryptionTokenDiscoveryDialog()
+{
+    if (_encryptionTokenDiscoveryDialog) {
+        return;
+    }
+
+    qCDebug(lcSystray) << "Opening an encryption token discovery dialog...";
+
+    const auto encryptionTokenDiscoveryDialog = new QQmlComponent(_trayEngine.get(), QStringLiteral("qrc:/qml/src/gui/tray/EncryptionTokenDiscoveryDialog.qml"));
+
+    if (encryptionTokenDiscoveryDialog->isError()) {
+        qCWarning(lcSystray) << encryptionTokenDiscoveryDialog->errorString();
+        return;
+    }
+
+    _encryptionTokenDiscoveryDialog = encryptionTokenDiscoveryDialog->createWithInitialProperties(QVariantMap{});
+}
+
+void Systray::destroyEncryptionTokenDiscoveryDialog()
+{
+    if (!_encryptionTokenDiscoveryDialog) {
+        return;
+    }
+    qCDebug(lcSystray) << "Closing an encryption token discovery dialog...";
+    _encryptionTokenDiscoveryDialog->deleteLater();
+    _encryptionTokenDiscoveryDialog = nullptr;
+}
+
 bool Systray::raiseDialogs()
 {
     return raiseFileDetailDialogs();
index 7b17bab423aae87dcc11ebd20b051ebb7ac02f81..9e013b7f52ac61e6a2261e6d4ac27ecd1ce683c6 100644 (file)
@@ -31,6 +31,8 @@ class QGuiApplication;
 
 namespace OCC {
 
+class ClientSideEncryptionTokenSelector;
+
 class AccessManagerFactory : public QQmlNetworkAccessManagerFactory
 {
 public:
@@ -115,6 +117,8 @@ signals:
     void syncIsPausedChanged();
     void isOpenChanged();
 
+    void hideSettingsDialog();
+
 public slots:
     void setTrayEngine(QQmlApplicationEngine *trayEngine);
     void create();
@@ -127,6 +131,8 @@ public slots:
     void createEditFileLocallyLoadingDialog(const QString &fileName);
     void destroyEditFileLocallyLoadingDialog();
     void createResolveConflictsDialog(const OCC::ActivityList &allConflicts);
+    void createEncryptionTokenDiscoveryDialog();
+    void destroyEncryptionTokenDiscoveryDialog();
 
     void slotCurrentUserChanged();
 
@@ -187,7 +193,9 @@ private:
 
     QSet<qlonglong> _callsAlreadyNotified;
     QPointer<QObject> _editFileLocallyLoadingDialog;
+    QPointer<QObject> _encryptionTokenDiscoveryDialog;
     QVector<QQuickWindow*> _fileDetailDialogs;
+    QQuickWindow* _tokenInitDialog = nullptr;
 
     QStringListModel _fakeActivityModel;
 };
diff --git a/src/gui/tray/EncryptionTokenDiscoveryDialog.qml b/src/gui/tray/EncryptionTokenDiscoveryDialog.qml
new file mode 100644 (file)
index 0000000..123cc75
--- /dev/null
@@ -0,0 +1,89 @@
+import QtQuick 2.15
+import QtQuick.Window 2.15
+import Style 1.0
+import com.nextcloud.desktopclient 1.0
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+
+ApplicationWindow {
+    id: root
+    flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
+
+    color: "transparent"
+
+    width: 320
+    height: contentLayout.implicitHeight
+    modality: Qt.ApplicationModal
+
+    readonly property real fontPixelSize: Style.topLinePixelSize * 1.5
+    readonly property real iconWidth: fontPixelSize * 2
+
+    // TODO: Rather than setting all these palette colours manually,
+    // create a custom style and do it for all components globally
+    palette {
+        text: Style.ncTextColor
+        windowText: Style.ncTextColor
+        buttonText: Style.ncTextColor
+        brightText: Style.ncTextBrightColor
+        highlight: Style.lightHover
+        highlightedText: Style.ncTextColor
+        light: Style.lightHover
+        midlight: Style.ncSecondaryTextColor
+        mid: Style.darkerHover
+        dark: Style.menuBorder
+        button: Style.buttonBackgroundColor
+        window: Style.backgroundColor
+        base: Style.backgroundColor
+        toolTipBase: Style.backgroundColor
+        toolTipText: Style.ncTextColor
+    }
+
+    Component.onCompleted: {
+        Systray.forceWindowInit(root);
+        x = Screen.width / 2 - width / 2
+        y = Screen.height / 2 - height / 2
+        root.show();
+        root.raise();
+        root.requestActivate();
+    }
+
+    Rectangle {
+        id: windowBackground
+        color: Style.backgroundColor
+        radius: Style.trayWindowRadius
+        border.color: palette.dark
+        anchors.fill: parent
+    }
+
+    ColumnLayout {
+        id: contentLayout
+        anchors.left: parent.left
+        anchors.right: parent.right
+        anchors.leftMargin: Style.standardSpacing
+        anchors.rightMargin: Style.standardSpacing
+        spacing: Style.standardSpacing
+
+        NCBusyIndicator {
+            id: busyIndicator
+            Layout.topMargin: Style.standardSpacing
+            Layout.alignment: Qt.AlignHCenter
+            Layout.preferredWidth: root.iconWidth
+            Layout.preferredHeight: root.iconWidth
+            imageSourceSizeHeight: root.iconWidth
+            imageSourceSizeWidth: root.iconWidth
+            padding: 0
+            color: palette.windowText
+            running: true
+        }
+        EnforcedPlainTextLabel {
+            id: labelMessage
+            Layout.alignment: Qt.AlignHCenter
+            Layout.fillWidth: true
+            Layout.bottomMargin: Style.standardSpacing
+            text: qsTr("Discovering the certificates stored on your USB token")
+            elide: Text.ElideRight
+            font.pixelSize: root.fontPixelSize
+            horizontalAlignment: Text.AlignHCenter
+        }
+    }
+}
index e613ad5bf5dd36ac893fd057175a5e984a64ab14..3bcd4281eeb5003780d4786cfca743641dca1492 100644 (file)
@@ -149,6 +149,7 @@ public:
     // Note that these are in the order we want to present them in the model!
     enum Type {
         DummyFetchingActivityType,
+        OpenSettingsNotificationType,
         NotificationType,
         SyncResultType,
         SyncFileItemType,
index b8c5d50ae1793b7fa49e32bedb564c4cafd02936..4adf4d5b30837968c6f325bb9dedf38e38b98124 100644 (file)
@@ -307,14 +307,14 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
         case Activity::DummyMoreActivitiesAvailableType:
             return "Activity";
         case Activity::NotificationType:
+        case Activity::OpenSettingsNotificationType:
             return "Notification";
         case Activity::SyncFileItemType:
             return "File";
         case Activity::SyncResultType:
             return "Sync";
-        default:
-            return QVariant();
         }
+        break;
     }
     case ActionTextRole:
         if(a._subjectDisplay.isEmpty()) {
@@ -358,7 +358,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
     case IsCurrentUserFileActivityRole:
         return a._isCurrentUserFileActivity;
     case ThumbnailRole: {
-        if (a._type == Activity::NotificationType && !a._talkNotificationData.userAvatar.isEmpty()) {
+        if ((a._type == Activity::NotificationType || a._type == Activity::OpenSettingsNotificationType) &&
+            !a._talkNotificationData.userAvatar.isEmpty()) {
             return generateAvatarThumbnailMap(a._talkNotificationData.userAvatar);
         }
 
@@ -383,7 +384,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
         return QVariant::fromValue(a);
     }
 
-    return QVariant();
+    return {};
 }
 
 int ActivityListModel::rowCount(const QModelIndex &parent) const
@@ -658,9 +659,10 @@ void ActivityListModel::removeActivityFromActivityList(const Activity &activity)
     }
 
     if (activity._type != Activity::ActivityType &&
-            activity._type != Activity::DummyFetchingActivityType &&
-            activity._type != Activity::DummyMoreActivitiesAvailableType &&
-            activity._type != Activity::NotificationType) {
+        activity._type != Activity::DummyFetchingActivityType &&
+        activity._type != Activity::DummyMoreActivitiesAvailableType &&
+        activity._type != Activity::NotificationType &&
+        activity._type != Activity::OpenSettingsNotificationType) {
 
         const auto notificationErrorsListIndex = _notificationErrorsLists.indexOf(activity);
         if (notificationErrorsListIndex != -1)
@@ -729,6 +731,8 @@ void ActivityListModel::slotTriggerDefaultAction(const int activityIndex)
         _currentInvalidFilenameDialog->open();
         ownCloudGui::raiseDialog(_currentInvalidFilenameDialog);
         return;
+    } else if (activity._type == Activity::OpenSettingsNotificationType) {
+        Q_EMIT showSettingsDialog();
     }
 
     if (!path.isEmpty()) {
index e8e4ed5bb93e053bfd4c5a2d297da90b12eb0b22..c00707876876fd7e0bf4625bf0e5ef0d6e0b4478 100644 (file)
@@ -152,6 +152,8 @@ signals:
 
     void interactiveActivityReceived();
 
+    void showSettingsDialog();
+
 protected:
     [[nodiscard]] bool currentlyFetching() const;
 
index abddeaa102d651ddb542930c50d3bc890bd6b548..b24ae990df3385e86e2e8c8b624cb995aea5ae05 100644 (file)
@@ -93,8 +93,27 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
     connect(_account->account().data(), &Account::capabilitiesChanged, this, &User::slotAccountCapabilitiesChangedRefreshGroupFolders);
 
     connect(_activityModel, &ActivityListModel::sendNotificationRequest, this, &User::slotSendNotificationRequest);
-    
+    connect(_activityModel, &ActivityListModel::showSettingsDialog,
+            Systray::instance(), &Systray::openSettings);
+
     connect(this, &User::sendReplyMessage, this, &User::slotSendReplyMessage);
+
+    connect(_account->account().data(), &Account::userCertificateNeedsMigrationChanged, this, [this] () {
+        auto certificateNeedMigration = Activity{};
+        certificateNeedMigration._type = Activity::OpenSettingsNotificationType;
+        certificateNeedMigration._subject = tr("End-to-end certificate needs to be migrated to a new one");
+        certificateNeedMigration._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate);
+        certificateNeedMigration._message = tr("Trigger the migration");
+        certificateNeedMigration._accName = _account->account()->displayName();
+        certificateNeedMigration._id = qHash("migrate-certificate");
+
+        _activityModel->removeActivityFromActivityList(certificateNeedMigration);
+
+        if (_account->account()->e2e()->userCertificateNeedsMigration()) {
+            _activityModel->addNotificationToActivityList(certificateNeedMigration);
+            showDesktopNotification(certificateNeedMigration);
+        }
+    });
 }
 
 void User::checkNotifiedNotifications()
index a215432063677148a519e83b4e42aa0b00cdd9c1..72ec2eedee479abe624315d3bd60d571ec2aa061 100644 (file)
@@ -12,6 +12,12 @@ if ( APPLE )
     )
 endif()
 
+if (WIN32)
+  list(APPEND OS_SPECIFIC_LINK_LIBRARIES
+    Crypt32
+  )
+endif()
+
 set(libsync_SRCS
     account.h
     account.cpp
@@ -123,6 +129,8 @@ set(libsync_SRCS
     clientsideencryptionjobs.cpp
     clientsideencryptionprimitives.h
     clientsideencryptionprimitives.cpp
+    clientsideencryptiontokenselector.h
+    clientsideencryptiontokenselector.cpp
     datetimeprovider.h
     datetimeprovider.cpp
     rootencryptedfolderinfo.h
@@ -205,6 +213,7 @@ target_link_libraries(nextcloudsync
   Nextcloud::csync
   OpenSSL::Crypto
   OpenSSL::SSL
+  PkgConfig::OPENSC-LIBP11
   ${OS_SPECIFIC_LINK_LIBRARIES}
   Qt::Core
   Qt::Network
index f45b70f211153018a6beb6569dfa56c3182b3241..fbfc415bddb4522c7babe30ec8f3eec73a668f52 100644 (file)
@@ -33,6 +33,8 @@
 #include "clientsideencryption.h"
 #include "ocsuserstatusconnector.h"
 
+#include "config.h"
+
 #include <QLoggingCategory>
 #include <QNetworkReply>
 #include <QNetworkAccessManager>
@@ -80,6 +82,9 @@ Account::Account(QObject *parent)
 
     _pushNotificationsReconnectTimer.setInterval(pushNotificationsReconnectInterval);
     connect(&_pushNotificationsReconnectTimer, &QTimer::timeout, this, &Account::trySetupPushNotifications);
+
+    connect(&_e2e, &ClientSideEncryption::userCertificateNeedsMigrationChanged,
+            this, &Account::userCertificateNeedsMigrationChanged);
 }
 
 AccountPtr Account::create()
@@ -1080,6 +1085,41 @@ bool Account::askUserForMnemonic() const
     return _e2eAskUserForMnemonic;
 }
 
+bool Account::enforceUseHardwareTokenEncryption() const
+{
+#if defined CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN
+    return CLIENTSIDEENCRYPTION_ENFORCE_USB_TOKEN;
+#else
+    return false;
+#endif
+}
+
+QString Account::encryptionHardwareTokenDriverPath() const
+{
+#if defined ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH
+    return ENCRYPTION_HARDWARE_TOKEN_DRIVER_PATH;
+#else
+    return {};
+#endif
+}
+
+QByteArray Account::encryptionCertificateFingerprint() const
+{
+    return _encryptionCertificateFingerprint;
+}
+
+void Account::setEncryptionCertificateFingerprint(const QByteArray &fingerprint)
+{
+    if (_encryptionCertificateFingerprint == fingerprint) {
+        return;
+    }
+
+    _encryptionCertificateFingerprint = fingerprint;
+    _e2e.usbTokenInformation()->setSha256Fingerprint(fingerprint);
+    Q_EMIT encryptionCertificateFingerprintChanged();
+    Q_EMIT wantsAccountSaved(this);
+}
+
 void Account::setAskUserForMnemonic(const bool ask)
 {
     _e2eAskUserForMnemonic = ask;
index dcf1fb623bd643369feb698fe91e5d7a7edd195d..eddb77c90510a6d5d44ecff25bd8d580ebd63f48 100644 (file)
@@ -102,6 +102,9 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject
     Q_PROPERTY(AccountNetworkTransferLimitSetting downloadLimitSetting READ downloadLimitSetting WRITE setDownloadLimitSetting NOTIFY downloadLimitSettingChanged)
     Q_PROPERTY(unsigned int uploadLimit READ uploadLimit WRITE setUploadLimit NOTIFY uploadLimitChanged)
     Q_PROPERTY(unsigned int downloadLimit READ downloadLimit WRITE setDownloadLimit NOTIFY downloadLimitChanged)
+    Q_PROPERTY(bool enforceUseHardwareTokenEncryption READ enforceUseHardwareTokenEncryption NOTIFY enforceUseHardwareTokenEncryptionChanged)
+    Q_PROPERTY(QString encryptionHardwareTokenDriverPath READ encryptionHardwareTokenDriverPath NOTIFY encryptionHardwareTokenDriverPathChanged)
+    Q_PROPERTY(QByteArray encryptionCertificateFingerprint READ encryptionCertificateFingerprint WRITE setEncryptionCertificateFingerprint NOTIFY encryptionCertificateFingerprintChanged)
 
 public:
     // We need to decide whether to use the client's global proxy settings or whether to use
@@ -412,6 +415,13 @@ public:
     [[nodiscard]] bool serverHasValidSubscription() const;
     void setServerHasValidSubscription(bool valid);
 
+    [[nodiscard]] bool enforceUseHardwareTokenEncryption() const;
+
+    [[nodiscard]] QString encryptionHardwareTokenDriverPath() const;
+
+    [[nodiscard]] QByteArray encryptionCertificateFingerprint() const;
+    void setEncryptionCertificateFingerprint(const QByteArray &fingerprint);
+
 public slots:
     /// Used when forgetting credentials
     void clearQNAMCache();
@@ -434,12 +444,16 @@ signals:
     // e.g. when the approved SSL certificates changed
     void wantsAccountSaved(OCC::Account *acc);
 
+    void wantsFoldersSynced();
+
     void serverVersionChanged(OCC::Account *account, const QString &newVersion, const QString &oldVersion);
 
     void accountChangedAvatar();
     void accountChangedDisplayName();
     void prettyNameChanged();
     void askUserForMnemonicChanged();
+    void enforceUseHardwareTokenEncryptionChanged();
+    void encryptionHardwareTokenDriverPathChanged();
 
     /// Used in RemoteWipe
     void appPasswordRetrieved(QString);
@@ -469,6 +483,9 @@ signals:
     void downloadLimitChanged();
     void termsOfServiceNeedToBeChecked();
 
+    void encryptionCertificateFingerprintChanged();
+    void userCertificateNeedsMigrationChanged();
+
 protected Q_SLOTS:
     void slotCredentialsFetched();
     void slotCredentialsAsked();
@@ -556,8 +573,8 @@ private:
     AccountNetworkTransferLimitSetting _downloadLimitSetting = AccountNetworkTransferLimitSetting::GlobalLimit;
     unsigned int _uploadLimit = 0;
     unsigned int _downloadLimit = 0;
-
     bool _serverHasValidSubscription = false;
+    QByteArray _encryptionCertificateFingerprint;
 
     /* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
      * TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
index 3b035f8b36d8fc698769eeab6b71f8182752e861..3f2a8a1d119dcd3e2036d3d47ccc3bcfd13c43fd 100644 (file)
@@ -1,12 +1,19 @@
-#include "clientsideencryption.h"
+/*
+ * Copyright Â© 2017, Tomaz Canabrava <tcanabrava@kde.org>
+ * Copyright Â© 2020, Andreas Jellinghaus <andreas@ionisiert.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
 
-#include <openssl/rsa.h>
-#include <openssl/evp.h>
-#include <openssl/cms.h>
-#include <openssl/pem.h>
-#include <openssl/err.h>
-#include <openssl/engine.h>
-#include <openssl/rand.h>
+#include "clientsideencryption.h"
 
 #include "account.h"
 #include "capabilities.h"
@@ -31,6 +38,8 @@
 #include <QXmlStreamNamespaceDeclaration>
 #include <QStack>
 #include <QInputDialog>
+#include <QMessageBox>
+#include <QWidget>
 #include <QLineEdit>
 #include <QIODevice>
 #include <QUuid>
 #include <QRandomGenerator>
 #include <QJsonArray>
 #include <QCryptographicHash>
+#include <QFutureWatcher>
+#include <QSslCertificate>
+#include <QSslCertificateExtension>
+
+#include <openssl/rsa.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/engine.h>
+#include <openssl/rand.h>
+#include <openssl/cms.h>
 
 #include <map>
 #include <string>
 #include <algorithm>
-
+#include <optional>
 #include <cstdio>
 
 QDebug operator<<(QDebug out, const std::string& str)
@@ -57,7 +77,8 @@ namespace OCC
 {
 
 Q_LOGGING_CATEGORY(lcCse, "nextcloud.sync.clientsideencryption", QtInfoMsg)
-Q_LOGGING_CATEGORY(lcCseDecryption, "nextcloud.e2e", QtInfoMsg)
+Q_LOGGING_CATEGORY(lcCseDecryption, "nextcloud.sync.clientsideencryption.decryption", QtInfoMsg)
+Q_LOGGING_CATEGORY(lcCseEncryption, "nextcloud.sync.clientsideencryption.encryption", QtInfoMsg)
 
 QString e2eeBaseUrl(const OCC::AccountPtr &account)
 {
@@ -311,14 +332,12 @@ QByteArray encryptPrivateKey(
 
     /* Create and initialise the context */
     if(!ctx) {
-        qCInfo(lcCse()) << "Error creating cipher";
-        handleErrors();
+        qCInfo(lcCse()) << "Error creating cipher" << handleErrors();
     }
 
     /* Initialise the decryption operation. */
     if(!EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr)) {
-        qCInfo(lcCse()) << "Error initializing context with aes_256";
-        handleErrors();
+        qCInfo(lcCse()) << "Error initializing context with aes_256" << handleErrors();
     }
 
     // No padding
@@ -326,14 +345,12 @@ QByteArray encryptPrivateKey(
 
     /* Set IV length. */
     if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr)) {
-        qCInfo(lcCse()) << "Error setting iv length";
-        handleErrors();
+        qCInfo(lcCse()) << "Error setting iv length" << handleErrors();
     }
 
     /* Initialise key and IV */
     if(!EVP_EncryptInit_ex(ctx, nullptr, nullptr, (unsigned char *)key.constData(), (unsigned char *)iv.constData())) {
-        qCInfo(lcCse()) << "Error initialising key and iv";
-        handleErrors();
+        qCInfo(lcCse()) << "Error initialising key and iv" << handleErrors();
     }
 
     // We write the base64 encoded private key
@@ -345,8 +362,7 @@ QByteArray encryptPrivateKey(
     // Do the actual encryption
     int len = 0;
     if(!EVP_EncryptUpdate(ctx, unsignedData(ctext), &len, (unsigned char *)privateKeyB64.constData(), privateKeyB64.size())) {
-        qCInfo(lcCse()) << "Error encrypting";
-        handleErrors();
+        qCInfo(lcCse()) << "Error encrypting" << handleErrors();
     }
 
     int clen = len;
@@ -355,16 +371,14 @@ QByteArray encryptPrivateKey(
      * this stage, but this does not occur in GCM mode
      */
     if(1 != EVP_EncryptFinal_ex(ctx, unsignedData(ctext) + len, &len)) {
-        qCInfo(lcCse()) << "Error finalizing encryption";
-        handleErrors();
+        qCInfo(lcCse()) << "Error finalizing encryption" << handleErrors();
     }
     clen += len;
 
     /* Get the e2EeTag */
     QByteArray e2EeTag(OCC::Constants::e2EeTagSize, '\0');
     if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, OCC::Constants::e2EeTagSize, unsignedData(e2EeTag))) {
-        qCInfo(lcCse()) << "Error getting the e2EeTag";
-        handleErrors();
+        qCInfo(lcCse()) << "Error getting the e2EeTag" << handleErrors();
     }
 
     QByteArray cipherTXT;
@@ -561,40 +575,86 @@ QByteArray privateKeyToPem(const QByteArray key) {
     return pem;
 }
 
-QByteArray encryptStringAsymmetric(const QSslKey key, const QByteArray &data)
+namespace internals {
+
+[[nodiscard]] std::optional<QByteArray> encryptStringAsymmetric(ENGINE *sslEngine,
+                                                                EVP_PKEY *publicKey,
+                                                                int pad_mode,
+                                                                const QByteArray& binaryData);
+
+[[nodiscard]] std::optional<QByteArray> decryptStringAsymmetric(ENGINE *sslEngine,
+                                                                EVP_PKEY *privateKey,
+                                                                int pad_mode,
+                                                                const QByteArray& binaryData);
+
+}
+
+std::optional<QByteArray> encryptStringAsymmetric(const CertificateInformation &selectedCertificate,
+                                                  const int paddingMode,
+                                                  const ClientSideEncryption &encryptionEngine,
+                                                  const QByteArray &binaryData)
 {
-    Q_ASSERT(!key.isNull());
-    if (key.isNull()) {
-        qCDebug(lcCse) << "Public key is null. Could not encrypt.";
+    if (!encryptionEngine.isInitialized()) {
+        qCWarning(lcCseDecryption()) << "end-to-end encryption is disabled";
         return {};
     }
-    Bio publicKeyBio;
-    const auto publicKeyPem = key.toPem();
-    BIO_write(publicKeyBio, publicKeyPem.constData(), publicKeyPem.size());
-    const auto publicKey = PKey::readPublicKey(publicKeyBio);
-    return EncryptionHelper::encryptStringAsymmetric(publicKey, data);
+
+    if (encryptionEngine.useTokenBasedEncryption()) {
+        qCDebug(lcCseEncryption()) << "use certificate on hardware token";
+    } else {
+        qCDebug(lcCseEncryption()) << "use certificate on software storage";
+    }
+
+    const auto publicKey = selectedCertificate.getEvpPublicKey();
+    Q_ASSERT(publicKey);
+
+    auto encryptedBase64Result = internals::encryptStringAsymmetric(encryptionEngine.sslEngine(), publicKey, paddingMode, binaryData);
+
+    if (!encryptedBase64Result) {
+        qCWarning(lcCseEncryption()) << "encrypt failed";
+        return {};
+    }
+
+    if (encryptedBase64Result->isEmpty()) {
+        qCDebug(lcCseEncryption()) << "ERROR. Could not encrypt data";
+        return {};
+    }
+
+    return encryptedBase64Result;
 }
 
-QByteArray decryptStringAsymmetric(const QByteArray &privateKeyPem, const QByteArray &data)
+std::optional<QByteArray> decryptStringAsymmetric(const CertificateInformation &selectedCertificate,
+                                                  const int paddingMode,
+                                                  const ClientSideEncryption &encryptionEngine,
+                                                  const QByteArray &base64Data)
 {
-    Q_ASSERT(!privateKeyPem.isEmpty());
-    if (privateKeyPem.isEmpty()) {
-        qCDebug(lcCse) << "Private key is empty. Could not encrypt.";
+    if (!encryptionEngine.isInitialized()) {
+        qCWarning(lcCseDecryption()) << "end-to-end encryption is disabled";
         return {};
     }
 
-    Bio privateKeyBio;
-    BIO_write(privateKeyBio, privateKeyPem.constData(), privateKeyPem.size());
-    const auto key = PKey::readPrivateKey(privateKeyBio);
+    if (encryptionEngine.useTokenBasedEncryption()) {
+        qCDebug(lcCseDecryption()) << "use certificate on hardware token";
+    } else {
+        qCDebug(lcCseDecryption()) << "use certificate on software storage";
+    }
+    const auto key = selectedCertificate.getEvpPrivateKey();
+    if (!key) {
+        qCWarning(lcCseDecryption()) << "invalid private key handle";
+        return {};
+    }
 
-    // Also base64 decode the result
-    const auto decryptResult = EncryptionHelper::decryptStringAsymmetric(key, data);
+    const auto decryptBase64Result = internals::decryptStringAsymmetric(encryptionEngine.sslEngine(), key, paddingMode, QByteArray::fromBase64(base64Data));
+    if (!decryptBase64Result) {
+        qCWarning(lcCseDecryption()) << "decrypt failed";
+        return {};
+    }
 
-    if (decryptResult.isEmpty()) {
-        qCDebug(lcCse()) << "ERROR. Could not decrypt data";
+    if (decryptBase64Result->isEmpty()) {
+        qCDebug(lcCseDecryption()) << "ERROR. Could not decrypt data";
         return {};
     }
-    return decryptResult;
+    return decryptBase64Result;
 }
 
 QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data) {
@@ -604,15 +664,13 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data)
 
     /* Create and initialise the context */
     if(!ctx) {
-        qCInfo(lcCse()) << "Error creating cipher";
-        handleErrors();
+        qCInfo(lcCse()) << "Error creating cipher" << handleErrors();
         return {};
     }
 
     /* Initialise the decryption operation. */
     if(!EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), nullptr, nullptr, nullptr)) {
-        qCInfo(lcCse()) << "Error initializing context with aes_128";
-        handleErrors();
+        qCInfo(lcCse()) << "Error initializing context with aes_128" << handleErrors();
         return {};
     }
 
@@ -621,15 +679,13 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data)
 
     /* Set IV length. */
     if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr)) {
-        qCInfo(lcCse()) << "Error setting iv length";
-        handleErrors();
+        qCInfo(lcCse()) << "Error setting iv length" << handleErrors();
         return {};
     }
 
     /* Initialise key and IV */
     if(!EVP_EncryptInit_ex(ctx, nullptr, nullptr, (unsigned char *)key.constData(), (unsigned char *)iv.constData())) {
-        qCInfo(lcCse()) << "Error initialising key and iv";
-        handleErrors();
+        qCInfo(lcCse()) << "Error initialising key and iv" << handleErrors();
         return {};
     }
 
@@ -642,8 +698,7 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data)
     // Do the actual encryption
     int len = 0;
     if(!EVP_EncryptUpdate(ctx, unsignedData(ctext), &len, (unsigned char *)dataB64.constData(), dataB64.size())) {
-        qCInfo(lcCse()) << "Error encrypting";
-        handleErrors();
+        qCInfo(lcCse()) << "Error encrypting" << handleErrors();
         return {};
     }
 
@@ -653,8 +708,7 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data)
      * this stage, but this does not occur in GCM mode
      */
     if(1 != EVP_EncryptFinal_ex(ctx, unsignedData(ctext) + len, &len)) {
-        qCInfo(lcCse()) << "Error finalizing encryption";
-        handleErrors();
+        qCInfo(lcCse()) << "Error finalizing encryption" << handleErrors();
         return {};
     }
     clen += len;
@@ -662,8 +716,7 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data)
     /* Get the e2EeTag */
     QByteArray e2EeTag(OCC::Constants::e2EeTagSize, '\0');
     if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, OCC::Constants::e2EeTagSize, unsignedData(e2EeTag))) {
-        qCInfo(lcCse()) << "Error getting the e2EeTag";
-        handleErrors();
+        qCInfo(lcCse()) << "Error getting the e2EeTag" << handleErrors();
         return {};
     }
 
@@ -679,122 +732,269 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data)
     return result;
 }
 
-QByteArray decryptStringAsymmetric(EVP_PKEY *privateKey, const QByteArray& data) {
+namespace internals {
+
+std::optional<QByteArray> decryptStringAsymmetric(ENGINE *sslEngine,
+                                                  EVP_PKEY *privateKey,
+                                                  int pad_mode,
+                                                  const QByteArray& binaryData) {
     int err = -1;
 
-    qCInfo(lcCseDecryption()) << "Start to work the decryption.";
-    auto ctx = PKeyCtx::forKey(privateKey, ENGINE_get_default_RSA());
+    auto ctx = PKeyCtx::forKey(privateKey, sslEngine);
     if (!ctx) {
-        qCInfo(lcCseDecryption()) << "Could not create the PKEY context.";
-        handleErrors();
+        qCInfo(lcCseDecryption()) << "Could not create the PKEY context." << handleErrors();
         return {};
     }
 
     err = EVP_PKEY_decrypt_init(ctx);
     if (err <= 0) {
-        qCInfo(lcCseDecryption()) << "Could not init the decryption of the metadata";
-        handleErrors();
+        qCInfo(lcCseDecryption()) << "Could not init the decryption of the metadata" << handleErrors();
         return {};
     }
 
-    if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
-        qCInfo(lcCseDecryption()) << "Error setting the encryption padding.";
-        handleErrors();
+    if (EVP_PKEY_CTX_set_rsa_padding(ctx, pad_mode) <= 0) {
+        qCInfo(lcCseDecryption()) << "Error setting the encryption padding." << handleErrors();
         return {};
     }
 
-    if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha256()) <= 0) {
-        qCInfo(lcCseDecryption()) << "Error setting OAEP SHA 256";
-        handleErrors();
+    if (pad_mode != RSA_PKCS1_PADDING && EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha1()) <= 0) {
+        qCInfo(lcCseDecryption()) << "Error setting OAEP SHA 256" << handleErrors();
         return {};
     }
 
-    if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha256()) <= 0) {
-        qCInfo(lcCseDecryption()) << "Error setting MGF1 padding";
-        handleErrors();
+    if (pad_mode != RSA_PKCS1_PADDING && EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha1()) <= 0) {
+        qCInfo(lcCseDecryption()) << "Error setting MGF1 padding" << handleErrors();
         return {};
     }
 
     size_t outlen = 0;
-    err = EVP_PKEY_decrypt(ctx, nullptr, &outlen,  (unsigned char *)data.constData(), data.size());
+    err = EVP_PKEY_decrypt(ctx, nullptr, &outlen,  (unsigned char *)binaryData.constData(), binaryData.size());
     if (err <= 0) {
-        qCInfo(lcCseDecryption()) << "Could not determine the buffer length";
-        handleErrors();
+        qCInfo(lcCseDecryption()) << "Could not determine the buffer length" << handleErrors();
         return {};
-    } else {
-        qCInfo(lcCseDecryption()) << "Size of output is: " << outlen;
-        qCInfo(lcCseDecryption()) << "Size of data is: " << data.size();
     }
 
     QByteArray out(static_cast<int>(outlen), '\0');
 
-    if (EVP_PKEY_decrypt(ctx, unsignedData(out), &outlen, (unsigned char *)data.constData(), data.size()) <= 0) {
+    if (EVP_PKEY_decrypt(ctx, unsignedData(out), &outlen, (unsigned char *)binaryData.constData(), binaryData.size()) <= 0) {
         const auto error = handleErrors();
         qCCritical(lcCseDecryption()) << "Could not decrypt the data." << error;
         return {};
-    } else {
-        qCInfo(lcCseDecryption()) << "data decrypted successfully";
     }
 
     // we don't need extra zeroes in out, so let's only return meaningful data
     out = QByteArray(out.constData(), outlen);
-
-    qCInfo(lcCse()) << out;
-    return out;
+    return out.toBase64();
 }
 
-QByteArray encryptStringAsymmetric(EVP_PKEY *publicKey, const QByteArray& data) {
-    int err = -1;
-
-    auto ctx = PKeyCtx::forKey(publicKey, ENGINE_get_default_RSA());
+std::optional<QByteArray> encryptStringAsymmetric(ENGINE *sslEngine,
+                                                  EVP_PKEY *publicKey,
+                                                  int pad_mode,
+                                                  const QByteArray& binaryData) {
+    auto ctx = PKeyCtx::forKey(publicKey, sslEngine);
     if (!ctx) {
-        qCInfo(lcCse()) << "Could not initialize the pkey context.";
-        exit(1);
+        qCInfo(lcCseEncryption()) << "Could not initialize the pkey context." << publicKey << sslEngine;
+        return {};
     }
 
     if (EVP_PKEY_encrypt_init(ctx) != 1) {
-        qCInfo(lcCse()) << "Error initilaizing the encryption.";
-        exit(1);
+        qCInfo(lcCseEncryption()) << "Error initilaizing the encryption." << handleErrors();
+        return {};
     }
 
-    if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
-        qCInfo(lcCse()) << "Error setting the encryption padding.";
-        exit(1);
+    if (EVP_PKEY_CTX_set_rsa_padding(ctx, pad_mode) <= 0) {
+        qCInfo(lcCseEncryption()) << "Error setting the encryption padding." << handleErrors();
+        return {};
     }
 
-    if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha256()) <= 0) {
-        qCInfo(lcCse()) << "Error setting OAEP SHA 256";
-        exit(1);
+    if (pad_mode != RSA_PKCS1_PADDING && EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha1()) <= 0) {
+        qCInfo(lcCseEncryption()) << "Error setting OAEP SHA 256" << handleErrors();
+        return {};
     }
 
-    if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha256()) <= 0) {
-        qCInfo(lcCse()) << "Error setting MGF1 padding";
-        exit(1);
+    if (pad_mode != RSA_PKCS1_PADDING && EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha1()) <= 0) {
+        qCInfo(lcCseEncryption()) << "Error setting MGF1 padding" << handleErrors();
+        return {};
     }
 
     size_t outLen = 0;
-    if (EVP_PKEY_encrypt(ctx, nullptr, &outLen, (unsigned char *)data.constData(), data.size()) != 1) {
-        qCInfo(lcCse()) << "Error retrieving the size of the encrypted data";
-        exit(1);
-    } else {
-        qCInfo(lcCse()) << "Encryption Length:" << outLen;
+    if (EVP_PKEY_encrypt(ctx, nullptr, &outLen, (unsigned char *)binaryData.constData(), binaryData.size()) != 1) {
+        qCInfo(lcCseEncryption()) << "Error retrieving the size of the encrypted data" << handleErrors();
+        return {};
     }
 
     QByteArray out(static_cast<int>(outLen), '\0');
-    if (EVP_PKEY_encrypt(ctx, unsignedData(out), &outLen, (unsigned char *)data.constData(), data.size()) != 1) {
-        qCInfo(lcCse()) << "Could not encrypt key." << err;
-        exit(1);
+    if (EVP_PKEY_encrypt(ctx, unsignedData(out), &outLen, (unsigned char *)binaryData.constData(), binaryData.size()) != 1) {
+        qCInfo(lcCseEncryption()) << "Could not encrypt key." << handleErrors();
+        return {};
     }
 
-    qCInfo(lcCse()) << out.toBase64();
-    return out;
+    // Transform the encrypted data into base64.
+    return out.toBase64();
+}
+
+}
+
+void debugOpenssl()
+{
+    if (ERR_peek_error() == 0) {
+        return;
+    }
+
+    const char *file;
+    char errorMessage[255];
+    int line;
+    while (const auto errorNumber = ERR_get_error_line(&file, &line)) {
+        ERR_error_string(errorNumber, errorMessage);
+        qCWarning(lcCse()) << errorMessage << file << line;
+    }
+}
+
+}
+
+
+ClientSideEncryption::ClientSideEncryption()
+{
 }
 
+bool ClientSideEncryption::isInitialized() const
+{
+    return useTokenBasedEncryption() || !getMnemonic().isEmpty();
 }
 
-ClientSideEncryption::ClientSideEncryption() = default;
+QSslKey ClientSideEncryption::getPublicKey() const
+{
+    return _encryptionCertificate.getSslPublicKey();
+}
 
-void ClientSideEncryption::initialize(const AccountPtr &account)
+const QByteArray &ClientSideEncryption::getPrivateKey() const
+{
+    return _encryptionCertificate.getPrivateKeyData();
+}
+
+void ClientSideEncryption::setPrivateKey(const QByteArray &privateKey)
+{
+    _encryptionCertificate.setPrivateKeyData(privateKey);
+}
+
+const CertificateInformation &ClientSideEncryption::getCertificateInformation() const
+{
+    return _encryptionCertificate;
+}
+
+CertificateInformation ClientSideEncryption::getCertificateInformationByFingerprint(const QByteArray &certificateFingerprint) const
+{
+    CertificateInformation result;
+
+    if (_encryptionCertificate.sha256Fingerprint() == certificateFingerprint) {
+        result = _encryptionCertificate;
+    } else {
+        for(const auto &oneCertificate : _otherCertificates) {
+            if (oneCertificate.sha256Fingerprint() == certificateFingerprint) {
+                result = oneCertificate;
+                break;
+            }
+        }
+    }
+
+    return result;
+}
+
+int ClientSideEncryption::paddingMode() const
+{
+    return RSA_PKCS1_PADDING;
+}
+
+CertificateInformation ClientSideEncryption::getTokenCertificateByFingerprint(const QByteArray &expectedFingerprint) const
+{
+    CertificateInformation result;
+
+    if (_encryptionCertificate.sha256Fingerprint() == expectedFingerprint) {
+        result = _encryptionCertificate;
+        return result;
+    }
+
+    const auto itCertificate = std::find_if(_otherCertificates.begin(), _otherCertificates.end(), [expectedFingerprint] (const auto &oneCertificate) {
+        return oneCertificate.sha256Fingerprint() == expectedFingerprint;
+    });
+    if (itCertificate != _otherCertificates.end()) {
+        result = *itCertificate;
+        return result;
+    }
+
+    return result;
+}
+
+bool ClientSideEncryption::useTokenBasedEncryption() const
+{
+    return _encryptionCertificate.getPkcs11PrivateKey();
+}
+
+const QString &ClientSideEncryption::getMnemonic() const
+{
+    return _mnemonic;
+}
+
+void ClientSideEncryption::setCertificate(const QSslCertificate &certificate)
+{
+    _encryptionCertificate = CertificateInformation{_encryptionCertificate.getPrivateKeyData(), QSslCertificate{certificate}};
+}
+
+const QSslCertificate& ClientSideEncryption::getCertificate() const
+{
+    return _encryptionCertificate.getCertificate();
+}
+
+ENGINE* ClientSideEncryption::sslEngine() const
+{
+    return ENGINE_get_default_RSA();
+}
+
+ClientSideEncryptionTokenSelector *ClientSideEncryption::usbTokenInformation()
+{
+    return &_usbTokenInformation;
+}
+
+bool ClientSideEncryption::canEncrypt() const
+{
+    if (!isInitialized()) {
+        return false;
+    }
+    if (useTokenBasedEncryption()) {
+        return _encryptionCertificate.canEncrypt();
+    }
+
+    return true;
+}
+
+bool ClientSideEncryption::canDecrypt() const
+{
+    return isInitialized();
+}
+
+bool ClientSideEncryption::userCertificateNeedsMigration() const
+{
+    if (!isInitialized()) {
+        return false;
+    }
+    if (useTokenBasedEncryption()) {
+        return _encryptionCertificate.userCertificateNeedsMigration();
+    }
+
+    return false;
+}
+
+QByteArray ClientSideEncryption::certificateSha256Fingerprint() const
+{
+    if (useTokenBasedEncryption()) {
+        return _encryptionCertificate.sha256Fingerprint();
+    }
+
+    return {};
+}
+
+void ClientSideEncryption::initialize(QWidget *settingsDialog,
+                                      const AccountPtr &account)
 {
     Q_ASSERT(account);
 
@@ -805,7 +1005,265 @@ void ClientSideEncryption::initialize(const AccountPtr &account)
         return;
     }
 
-    fetchCertificateFromKeyChain(account);
+    if (account->enforceUseHardwareTokenEncryption()) {
+        addExtraRootCertificates();
+        if (_usbTokenInformation.isSetup()) {
+            initializeHardwareTokenEncryption(settingsDialog, account);
+        } else if (account->e2eEncryptionKeysGenerationAllowed() && account->askUserForMnemonic()) {
+            Q_EMIT startingDiscoveryEncryptionUsbToken();
+            auto futureTokenDiscoveryResult = new QFutureWatcher<void>(this);
+            auto tokenDiscoveryResult = _usbTokenInformation.searchForCertificates(account);
+            futureTokenDiscoveryResult->setFuture(tokenDiscoveryResult);
+            connect(futureTokenDiscoveryResult, &QFutureWatcher<void>::finished,
+                    this, [this, settingsDialog, account, futureTokenDiscoveryResult] () {
+                completeHardwareTokenInitialization(settingsDialog, account);
+                futureTokenDiscoveryResult->deleteLater();
+                Q_EMIT finishedDiscoveryEncryptionUsbToken();
+            });
+        } else {
+            emit initializationFinished();
+        }
+    } else {
+        fetchCertificateFromKeyChain(account);
+    }
+}
+
+void ClientSideEncryption::addExtraRootCertificates()
+{
+#if defined(Q_OS_WIN)
+    auto sslConfig = QSslConfiguration::defaultConfiguration();
+
+    for (const auto &storeName : std::vector<std::wstring>{L"CA"}) {
+        auto systemStore = CertOpenSystemStore(0, storeName.data());
+        if (systemStore) {
+            auto certificatePointer = PCCERT_CONTEXT{nullptr};
+            while (true) {
+                certificatePointer = CertFindCertificateInStore(systemStore, X509_ASN_ENCODING, 0, CERT_FIND_ANY, nullptr, certificatePointer);
+                if (!certificatePointer) {
+                    break;
+                }
+                const auto der = QByteArray{reinterpret_cast<const char *>(certificatePointer->pbCertEncoded),
+                                            static_cast<int>(certificatePointer->cbCertEncoded)};
+                const auto cert = QSslCertificate{der, QSsl::Der};
+
+                qCDebug(lcCse()) << "found certificate" << cert.subjectDisplayName() << cert.issuerDisplayName() << "from store" << storeName;
+
+                sslConfig.addCaCertificate(cert);
+            }
+            CertCloseStore(systemStore, 0);
+        }
+    }
+
+    QSslConfiguration::setDefaultConfiguration(sslConfig);
+#endif
+
+    qCDebug(lcCse()) << "existing CA certificates";
+    const auto currentSslConfig = QSslConfiguration::defaultConfiguration();
+    const auto &caCertificates = currentSslConfig.caCertificates();
+    for (const auto &oneCaCertificate : caCertificates) {
+        qCDebug(lcCse()) << oneCaCertificate.subjectDisplayName() << oneCaCertificate.issuerDisplayName();
+    }
+}
+
+void ClientSideEncryption::initializeHardwareTokenEncryption(QWidget *settingsDialog,
+                                                             const AccountPtr &account)
+{
+    auto ctx = Pkcs11Context{Pkcs11Context::State::CreateContext};
+    _tokenSlots.reset();
+    _encryptionCertificate.clear();
+    _otherCertificates.clear();
+    _context.clear();
+
+    if (PKCS11_CTX_load(ctx, account->encryptionHardwareTokenDriverPath().toLatin1().constData())) {
+        qCWarning(lcCse()) << "loading pkcs11 engine failed:" << ERR_reason_error_string(ERR_get_error());
+
+        failedToInitialize(account);
+        return;
+    }
+
+    auto tokensCount = 0u;
+    PKCS11_SLOT *tempTokenSlots = nullptr;
+    /* get information on all slots */
+    if (PKCS11_enumerate_slots(ctx, &tempTokenSlots, &tokensCount) < 0) {
+        qCWarning(lcCse()) << "no slots available" << ERR_reason_error_string(ERR_get_error());
+
+        failedToInitialize(account);
+        return;
+    }
+
+    auto deleter = [ctx = static_cast<PKCS11_CTX*>(ctx), tokensCount] (PKCS11_SLOT* pointer) noexcept -> void {
+        qCWarning(lcCse()) << "destructor" << pointer << ctx;
+        if (pointer) {
+            qCWarning(lcCse()) << "destructor" << pointer << ctx;
+            PKCS11_release_all_slots(ctx, pointer, tokensCount);
+        }
+    };
+
+    auto tokenSlots = decltype(_tokenSlots){tempTokenSlots, deleter};
+
+    auto currentSlot = static_cast<PKCS11_SLOT*>(nullptr);
+    for(auto i = 0u; i < tokensCount; ++i) {
+        currentSlot = PKCS11_find_next_token(ctx, tokenSlots.get(), tokensCount, currentSlot);
+        if (currentSlot == nullptr || currentSlot->token == nullptr) {
+            break;
+        }
+
+        qCDebug(lcCse()) << "Slot manufacturer......:" << currentSlot->manufacturer;
+        qCDebug(lcCse()) << "Slot description.......:" << currentSlot->description;
+        qCDebug(lcCse()) << "Slot token label.......:" << currentSlot->token->label;
+        qCDebug(lcCse()) << "Slot token manufacturer:" << currentSlot->token->manufacturer;
+        qCDebug(lcCse()) << "Slot token model.......:" << currentSlot->token->model;
+        qCDebug(lcCse()) << "Slot token serialnr....:" << currentSlot->token->serialnr;
+
+        if (PKCS11_open_session(currentSlot, 0) != 0) {
+            qCWarning(lcCse()) << "PKCS11_open_session failed" << ERR_reason_error_string(ERR_get_error());
+
+            failedToInitialize(account);
+            return;
+        }
+
+        auto logged_in = 0;
+        if (PKCS11_is_logged_in(currentSlot, 0, &logged_in) != 0) {
+            qCWarning(lcCse()) << "PKCS11_is_logged_in failed" << ERR_reason_error_string(ERR_get_error());
+
+            failedToInitialize(account);
+            return;
+        }
+
+        while (true) {
+            auto pinHasToBeCached = false;
+            auto newPin = _cachedPin;
+
+            if (newPin.isEmpty()) {
+                /* perform pkcs #11 login */
+                bool ok;
+                newPin = QInputDialog::getText(settingsDialog,
+                                               tr("PIN needed to login to token"),
+                                               tr("Enter Certificate USB Token PIN:"),
+                                               QLineEdit::Password,
+                                               {},
+                                               &ok);
+                if (!ok || newPin.isEmpty()) {
+                    qCWarning(lcCse()) << "an USER pin is required";
+
+                    Q_EMIT initializationFinished();
+                    return;
+                }
+
+                pinHasToBeCached = true;
+            }
+
+            const auto newPinData = newPin.toLatin1();
+            if (PKCS11_login(currentSlot, 0, newPinData.data()) != 0) {
+                QMessageBox::warning(settingsDialog,
+                                     tr("Invalid PIN. Login failed"),
+                                     tr("Login to the token failed after providing the user PIN. It may be invalid or wrong. Please try again !"),
+                                     QMessageBox::Ok);
+                _cachedPin.clear();
+                continue;
+            }
+
+            /* check if user is logged in */
+            if (PKCS11_is_logged_in(currentSlot, 0, &logged_in) != 0) {
+                qCWarning(lcCse()) << "PKCS11_is_logged_in failed" << ERR_reason_error_string(ERR_get_error());
+
+                _cachedPin.clear();
+                failedToInitialize(account);
+                return;
+            }
+            if (!logged_in) {
+                qCWarning(lcCse()) << "PKCS11_is_logged_in says user is not logged in, expected to be logged in";
+
+                _cachedPin.clear();
+                failedToInitialize(account);
+                return;
+            }
+
+            if (pinHasToBeCached) {
+                cacheTokenPin(newPin);
+            }
+
+            break;
+        }
+
+        auto keysCount = 0u;
+        auto certificatesFromToken = static_cast<PKCS11_CERT*>(nullptr);
+        if (PKCS11_enumerate_certs(currentSlot->token, &certificatesFromToken, &keysCount)) {
+            qCWarning(lcCse()) << "PKCS11_enumerate_certs failed" << ERR_reason_error_string(ERR_get_error());
+
+            failedToInitialize(account);
+            return;
+        }
+
+        for (auto certificateIndex = 0u; certificateIndex < keysCount; ++certificateIndex) {
+            const auto currentCertificate = &certificatesFromToken[certificateIndex];
+
+            Bio out;
+            const auto ret = PEM_write_bio_X509(out, currentCertificate->x509);
+            if (ret <= 0){
+                qCWarning(lcCse()) << "PEM_write_bio_X509 failed" << ERR_reason_error_string(ERR_get_error());
+
+                failedToInitialize(account);
+                return;
+            }
+
+            const auto result = BIO2ByteArray(out);
+            auto sslCertificate = QSslCertificate{result, QSsl::Pem};
+
+            if (sslCertificate.isSelfSigned()) {
+                qCDebug(lcCse()) << "newly found certificate is self signed: goint to ignore it";
+                continue;
+            }
+
+            const auto certificateKey = PKCS11_find_key(currentCertificate);
+            if (!certificateKey) {
+                qCWarning(lcCse()) << "PKCS11_find_key failed" << ERR_reason_error_string(ERR_get_error());
+
+                failedToInitialize(account);
+                return;
+            }
+
+            qCDebug(lcCse) << "checking the type of the key associated to the certificate";
+            qCDebug(lcCse) << "key type" << Qt::hex << PKCS11_get_key_type(certificateKey);
+
+            _otherCertificates.emplace_back(certificateKey, std::move(sslCertificate));
+        }
+    }
+
+    for (const auto &oneCertificateInformation : _otherCertificates) {
+        if (oneCertificateInformation.isSelfSigned()) {
+            qCDebug(lcCse()) << "newly found certificate is self signed: goint to ignore it";
+            continue;
+        }
+
+        if (!_usbTokenInformation.sha256Fingerprint().isEmpty() && oneCertificateInformation.sha256Fingerprint() != _usbTokenInformation.sha256Fingerprint()) {
+            qCDebug(lcCse()) << "skipping certificate from" << "with fingerprint" << oneCertificateInformation.sha256Fingerprint() << "different from" << _usbTokenInformation.sha256Fingerprint();
+            continue;
+        }
+
+        const auto &sslErrors = oneCertificateInformation.verify();
+        for (const auto &sslError : sslErrors) {
+            qCInfo(lcCse()) << "certificate validation error" << sslError;
+        }
+
+        setEncryptionCertificate(oneCertificateInformation);
+
+        if (canEncrypt() && !checkEncryptionIsWorking()) {
+            qCWarning(lcCse()) << "encryption is not properly setup";
+
+            failedToInitialize(account);
+            return;
+        }
+
+        sendPublicKey(account);
+
+        _tokenSlots = std::move(tokenSlots);
+        _context = std::move(ctx);
+
+        return;
+    }
+
+    failedToInitialize(account);
 }
 
 void ClientSideEncryption::fetchCertificateFromKeyChain(const AccountPtr &account)
@@ -857,18 +1315,24 @@ bool ClientSideEncryption::checkPublicKeyValidity(const AccountPtr &account) con
     QByteArray data = EncryptionHelper::generateRandom(64);
 
     Bio publicKeyBio;
-    QByteArray publicKeyPem = account->e2e()->_publicKey.toPem();
+    QByteArray publicKeyPem = account->e2e()->getPublicKey().toPem();
     BIO_write(publicKeyBio, publicKeyPem.constData(), publicKeyPem.size());
     auto publicKey = PKey::readPublicKey(publicKeyBio);
 
-    auto encryptedData = EncryptionHelper::encryptStringAsymmetric(publicKey, data.toBase64());
+    auto encryptedData = EncryptionHelper::encryptStringAsymmetric(account->e2e()->getCertificateInformation(), account->e2e()->paddingMode(), *account->e2e(), data.toBase64());
+    if (!encryptedData) {
+        qCWarning(lcCse()) << "encryption error";
+        return false;
+    }
 
-    Bio privateKeyBio;
-    QByteArray privateKeyPem = account->e2e()->_privateKey;
-    BIO_write(privateKeyBio, privateKeyPem.constData(), privateKeyPem.size());
-    auto key = PKey::readPrivateKey(privateKeyBio);
+    auto key = _encryptionCertificate.getEvpPrivateKey();
 
-    QByteArray decryptResult = QByteArray::fromBase64(EncryptionHelper::decryptStringAsymmetric(key, encryptedData));
+    const auto decryptionResult = EncryptionHelper::decryptStringAsymmetric(account->e2e()->getCertificateInformation(), account->e2e()->paddingMode(), *account->e2e(), *encryptedData);
+    if (!decryptionResult) {
+        qCWarning(lcCse()) << "encryption error";
+        return false;
+    }
+    const auto decryptResult = QByteArray::fromBase64(*decryptionResult);
 
     if (data != decryptResult) {
         qCInfo(lcCse()) << "invalid private key";
@@ -878,6 +1342,39 @@ bool ClientSideEncryption::checkPublicKeyValidity(const AccountPtr &account) con
     return true;
 }
 
+bool ClientSideEncryption::checkEncryptionIsWorking() const
+{
+    qCInfo(lcCse) << "check encryption is working before enabling end-to-end encryption feature";
+    QByteArray data = EncryptionHelper::generateRandom(64);
+
+    auto encryptedData = EncryptionHelper::encryptStringAsymmetric(getCertificateInformation(), paddingMode(), *this, data);
+    if (!encryptedData) {
+        qCWarning(lcCse()) << "encryption error";
+        return false;
+    }
+
+    qCDebug(lcCse) << "encryption is working with" << getCertificateInformation().sha256Fingerprint();
+
+    const auto decryptionResult = EncryptionHelper::decryptStringAsymmetric(getCertificateInformation(), paddingMode(), *this, *encryptedData);
+    if (!decryptionResult) {
+        qCWarning(lcCse()) << "encryption error";
+        return false;
+    }
+
+    qCDebug(lcCse) << "decryption is working with" << getCertificateInformation().sha256Fingerprint();
+
+    QByteArray decryptResult = QByteArray::fromBase64(*decryptionResult);
+
+    if (data != decryptResult) {
+        qCInfo(lcCse()) << "recovered data does not match the initial data after encryption and decryption of it";
+        return false;
+    }
+
+    qCInfo(lcCse) << "end-to-end encryption is working with" << getCertificateInformation().sha256Fingerprint();
+
+    return true;
+}
+
 bool ClientSideEncryption::checkServerPublicKeyValidity(const QByteArray &serverPublicKeyString) const
 {
     Bio serverPublicKeyBio;
@@ -885,7 +1382,7 @@ bool ClientSideEncryption::checkServerPublicKeyValidity(const QByteArray &server
     const auto serverPublicKey = PKey::readPrivateKey(serverPublicKeyBio);
 
     Bio certificateBio;
-    const auto certificatePem = _certificate.toPem();
+    const auto certificatePem = _encryptionCertificate.getCertificate().toPem();
     BIO_write(certificateBio, certificatePem.constData(), certificatePem.size());
     const auto x509Certificate = X509Certificate::readCertificate(certificateBio);
     if (!x509Certificate) {
@@ -914,15 +1411,13 @@ void ClientSideEncryption::publicCertificateFetched(Job *incoming)
         return;
     }
 
-    _certificate = QSslCertificate(readJob->binaryData(), QSsl::Pem);
+    _encryptionCertificate = CertificateInformation{_encryptionCertificate.getPrivateKeyData(), QSslCertificate{readJob->binaryData(), QSsl::Pem}};
 
-    if (_certificate.isNull()) {
+    if (_encryptionCertificate.getCertificate().isNull()) {
         fetchPublicKeyFromKeyChain(account);
         return;
     }
 
-    _publicKey = _certificate.publicKey();
-
     qCInfo(lcCse()) << "Public key fetched from keychain";
 
     const QString kck = AbstractCredentials::keychainKey(
@@ -942,7 +1437,7 @@ void ClientSideEncryption::publicCertificateFetched(Job *incoming)
 QByteArray ClientSideEncryption::generateSignatureCryptographicMessageSyntax(const QByteArray &data) const
 {
     Bio certificateBio;
-    const auto certificatePem = _certificate.toPem();
+    const auto certificatePem = _encryptionCertificate.getCertificate().toPem();
     BIO_write(certificateBio, certificatePem.constData(), certificatePem.size());
     const auto x509Certificate = X509Certificate::readCertificate(certificateBio);
     if (!x509Certificate) {
@@ -950,15 +1445,14 @@ QByteArray ClientSideEncryption::generateSignatureCryptographicMessageSyntax(con
         return {};
     }
 
-    Bio privateKeyBio;
-    BIO_write(privateKeyBio, _privateKey.constData(), _privateKey.size());
-    const auto privateKey = PKey::readPrivateKey(privateKeyBio);
+    const auto privateKey = _encryptionCertificate.getEvpPrivateKey();
 
     Bio dataBio;
     BIO_write(dataBio, data.constData(), data.size());
 
     const auto contentInfo = CMS_sign(x509Certificate, privateKey, nullptr, dataBio, CMS_DETACHED);
 
+    Q_ASSERT(contentInfo);
     if (!contentInfo) {
         return {};
     }
@@ -1038,7 +1532,7 @@ void ClientSideEncryption::publicKeyFetched(QKeychain::Job *incoming)
         return;
     }
 
-    _publicKey = publicKey;
+    Q_UNUSED(publicKey)
 
     const QString kck = AbstractCredentials::keychainKey(
         account->url().toString(),
@@ -1079,10 +1573,9 @@ void ClientSideEncryption::privateKeyFetched(Job *incoming)
         return;
     }
 
-    //_privateKey = QSslKey(readJob->binaryData(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
-    _privateKey = readJob->binaryData();
+    _encryptionCertificate.setPrivateKeyData(readJob->binaryData());
 
-    if (_privateKey.isNull()) {
+    if (getPrivateKey().isNull()) {
         getPrivateKeyFromServer(account);
         return;
     }
@@ -1116,9 +1609,9 @@ void ClientSideEncryption::mnemonicKeyFetched(QKeychain::Job *incoming)
         return;
     }
 
-    _mnemonic = readJob->textData();
+    setMnemonic(readJob->textData());
 
-    qCInfo(lcCse()) << "Mnemonic key fetched from keychain: " << _mnemonic;
+    qCInfo(lcCse()) << "Mnemonic key fetched from keychain";
 
     checkServerHasSavedKeys(account);
 }
@@ -1134,7 +1627,7 @@ void ClientSideEncryption::writePrivateKey(const AccountPtr &account)
     auto *job = new WritePasswordJob(Theme::instance()->appName());
     job->setInsecureFallback(false);
     job->setKey(kck);
-    job->setBinaryData(_privateKey);
+    job->setBinaryData(getPrivateKey());
     connect(job, &WritePasswordJob::finished, [](Job *incoming) {
         Q_UNUSED(incoming);
         qCInfo(lcCse()) << "Private key stored in keychain";
@@ -1153,7 +1646,7 @@ void ClientSideEncryption::writeCertificate(const AccountPtr &account)
     auto *job = new WritePasswordJob(Theme::instance()->appName());
     job->setInsecureFallback(false);
     job->setKey(kck);
-    job->setBinaryData(_certificate.toPem());
+    job->setBinaryData(_encryptionCertificate.getCertificate().toPem());
     connect(job, &WritePasswordJob::finished, [](Job *incoming) {
         Q_UNUSED(incoming);
         qCInfo(lcCse()) << "Certificate stored in keychain";
@@ -1177,10 +1670,50 @@ void ClientSideEncryption::writeCertificate(const AccountPtr &account, const QSt
     job->start();
 }
 
+void ClientSideEncryption::completeHardwareTokenInitialization(QWidget *settingsDialog,
+                                                               const OCC::AccountPtr &account)
+{
+    if (_usbTokenInformation.isSetup()) {
+        initializeHardwareTokenEncryption(settingsDialog, account);
+    } else {
+        emit initializationFinished();
+    }
+}
+
+void ClientSideEncryption::setMnemonic(const QString &mnemonic)
+{
+    if (_mnemonic == mnemonic) {
+        return;
+    }
+
+    _mnemonic = mnemonic;
+
+    Q_EMIT canEncryptChanged();
+    Q_EMIT canDecryptChanged();
+}
+
+void ClientSideEncryption::setEncryptionCertificate(CertificateInformation certificateInfo)
+{
+    if (_encryptionCertificate == certificateInfo) {
+        return;
+    }
+
+    const auto oldValueForUserCertificateNeedsMigration = _encryptionCertificate.userCertificateNeedsMigration();
+
+    _encryptionCertificate = std::move(certificateInfo);
+
+    Q_EMIT canEncryptChanged();
+    Q_EMIT canDecryptChanged();
+
+    if (oldValueForUserCertificateNeedsMigration != _encryptionCertificate.userCertificateNeedsMigration()) {
+        Q_EMIT userCertificateNeedsMigrationChanged();
+    }
+}
+
 void ClientSideEncryption::generateMnemonic()
 {
     const auto list = WordList::getRandomWords(12);
-    _mnemonic = list.join(' ');
+    setMnemonic(list.join(' '));
 }
 
 template <typename L>
@@ -1222,6 +1755,10 @@ void ClientSideEncryption::forgetSensitiveData(const AccountPtr &account)
         return job;
     };
 
+    if (!account->credentials()) {
+        return;
+    }
+
     const auto user = account->credentials()->user();
     const auto deletePrivateKeyJob = createDeleteJob(user + e2e_private);
     const auto deleteCertJob = createDeleteJob(user + e2e_cert);
@@ -1233,6 +1770,15 @@ void ClientSideEncryption::forgetSensitiveData(const AccountPtr &account)
     deletePrivateKeyJob->start();
     deleteCertJob->start();
     deleteMnemonicJob->start();
+    _usbTokenInformation.setSha256Fingerprint({});
+    account->setEncryptionCertificateFingerprint({});
+    _tokenSlots.reset();
+    _encryptionCertificate.clear();
+    _otherCertificates.clear();
+    _context.clear();
+    Q_EMIT canDecryptChanged();
+    Q_EMIT canEncryptChanged();
+    Q_EMIT userCertificateNeedsMigrationChanged();
 }
 
 void ClientSideEncryption::getUsersPublicKeyFromServer(const AccountPtr &account, const QStringList &userIds)
@@ -1264,6 +1810,11 @@ void ClientSideEncryption::getUsersPublicKeyFromServer(const AccountPtr &account
     job->start();
 }
 
+void ClientSideEncryption::migrateCertificate()
+{
+    _usbTokenInformation.clear();
+}
+
 void ClientSideEncryption::handlePrivateKeyDeleted(const QKeychain::Job* const incoming)
 {
     const auto error = incoming->error();
@@ -1273,7 +1824,8 @@ void ClientSideEncryption::handlePrivateKeyDeleted(const QKeychain::Job* const i
     }
 
     qCDebug(lcCse) << "Private key successfully deleted from keychain. Clearing.";
-    _privateKey = QByteArray();
+    _encryptionCertificate.clear();
+
     Q_EMIT privateKeyDeleted();
     checkAllSensitiveDataDeleted();
 }
@@ -1287,7 +1839,7 @@ void ClientSideEncryption::handleCertificateDeleted(const QKeychain::Job* const
     }
 
     qCDebug(lcCse) << "Certificate successfully deleted from keychain. Clearing.";
-    _certificate = QSslCertificate();
+    _encryptionCertificate.clear();
     Q_EMIT certificateDeleted();
     checkAllSensitiveDataDeleted();
 }
@@ -1301,7 +1853,7 @@ void ClientSideEncryption::handleMnemonicDeleted(const QKeychain::Job* const inc
     }
 
     qCDebug(lcCse) << "Mnemonic successfully deleted from keychain. Clearing.";
-    _mnemonic = QString();
+    setMnemonic({});
     Q_EMIT mnemonicDeleted();
     checkAllSensitiveDataDeleted();
 }
@@ -1314,14 +1866,13 @@ void ClientSideEncryption::handlePublicKeyDeleted(const QKeychain::Job * const i
         return;
     }
 
-    _publicKey.clear();
     Q_EMIT publicKeyDeleted();
     checkAllSensitiveDataDeleted();
 }
 
 bool ClientSideEncryption::sensitiveDataRemaining() const
 {
-    return !_privateKey.isEmpty() || !_certificate.isNull() || !_mnemonic.isEmpty();
+    return !getPrivateKey().isEmpty() || !_encryptionCertificate.getCertificate().isNull() || !_mnemonic.isEmpty() || !_usbTokenInformation.sha256Fingerprint().isEmpty() || _encryptionCertificate.sensitiveDataRemaining();
 }
 
 void ClientSideEncryption::failedToInitialize(const AccountPtr &account)
@@ -1330,12 +1881,25 @@ void ClientSideEncryption::failedToInitialize(const AccountPtr &account)
     Q_EMIT initializationFinished();
 }
 
+void ClientSideEncryption::saveCertificateIdentification(const AccountPtr &account) const
+{
+    account->setEncryptionCertificateFingerprint(_usbTokenInformation.sha256Fingerprint());
+}
+
+void ClientSideEncryption::cacheTokenPin(const QString pin)
+{
+    _cachedPin = pin;
+    QTimer::singleShot(86400000, [this] () {
+        _cachedPin.clear();
+    });
+}
+
 void ClientSideEncryption::checkAllSensitiveDataDeleted()
 {
     if (sensitiveDataRemaining()) {
         qCWarning(lcCse) << "Some sensitive data emaining:"
-                       << "Private key:" << (_privateKey.isEmpty() ? "is empty" : "is not empty")
-                         << "Certificate is null:" << (_certificate.isNull() ? "true" : "false")
+                       << "Private key:" << (getPrivateKey().isEmpty() ? "is empty" : "is not empty")
+                         << "Certificate is null:" << (_encryptionCertificate.getCertificate().isNull() ? "true" : "false")
                          << "Mnemonic:" << (_mnemonic.isEmpty() ? "is empty" : "is not empty");
         return;
     }
@@ -1381,7 +1945,7 @@ void ClientSideEncryption::generateKeyPair(const AccountPtr &account)
             return;
         }
 
-        _privateKey = BIO2ByteArray(privKey);
+        _encryptionCertificate.setPrivateKeyData(BIO2ByteArray(privKey));
     }
 
     Bio privKey;
@@ -1475,13 +2039,12 @@ void ClientSideEncryption::sendSignRequestCSR(const AccountPtr &account,
     auto job = new SignPublicKeyApiJob(account, e2eeBaseUrl(account) + "public-key", this);
     job->setCsr(csrContent);
 
-    connect(job, &SignPublicKeyApiJob::jsonReceived, [this, account, keyPair = std::move(keyPair)](const QJsonDocument& json, const int retCode) {
+    connect(job, &SignPublicKeyApiJob::jsonReceived, job, [this, account, keyPair = std::move(keyPair)](const QJsonDocument& json, const int retCode) {
         if (retCode == 200) {
             const auto cert = json.object().value("ocs").toObject().value("data").toObject().value("public-key").toString();
-            _certificate = QSslCertificate(cert.toLocal8Bit(), QSsl::Pem);
-            _publicKey = _certificate.publicKey();
+            _encryptionCertificate = CertificateInformation{_encryptionCertificate.getPrivateKeyData(), QSslCertificate{cert.toLocal8Bit(), QSsl::Pem}};
             Bio certificateBio;
-            const auto certificatePem = _certificate.toPem();
+            const auto certificatePem = _encryptionCertificate.getCertificate().toPem();
             BIO_write(certificateBio, certificatePem.constData(), certificatePem.size());
             const auto x509Certificate = X509Certificate::readCertificate(certificateBio);
             if (!X509_check_private_key(x509Certificate, keyPair)) {
@@ -1503,6 +2066,28 @@ void ClientSideEncryption::sendSignRequestCSR(const AccountPtr &account,
     job->start();
 }
 
+void ClientSideEncryption::sendPublicKey(const AccountPtr &account)
+{
+    // Send public key to the server
+    auto job = new StorePublicKeyApiJob(account, e2eeBaseUrl(account) + "public-key", this);
+    job->setPublicKey(_encryptionCertificate.getCertificate().toPem());
+    connect(job, &StorePublicKeyApiJob::jsonReceived, [this, account](const QJsonDocument& doc, int retCode) {
+        Q_UNUSED(doc);
+        switch(retCode) {
+        case 200:
+        case 409:
+            saveCertificateIdentification(account);
+            emit initializationFinished();
+
+            break;
+        default:
+            qCWarning(lcCse) << "Store certificate failed, return code:" << retCode;
+            failedToInitialize(account);
+        }
+    });
+    job->start();
+}
+
 void ClientSideEncryption::writeKeyPair(const AccountPtr &account,
                                         PKey keyPair,
                                         const QByteArray &csrContent)
@@ -1627,7 +2212,7 @@ void ClientSideEncryption::encryptPrivateKey(const AccountPtr &account)
 
     auto salt = EncryptionHelper::generateRandom(40);
     auto secretKey = EncryptionHelper::generatePassword(passPhrase, salt);
-    auto cryptedText = EncryptionHelper::encryptPrivateKey(secretKey, EncryptionHelper::privateKeyToPem(_privateKey), salt);
+    auto cryptedText = EncryptionHelper::encryptPrivateKey(secretKey, EncryptionHelper::privateKeyToPem(getPrivateKey()), salt);
 
     // Send private key to the server
     auto job = new StorePrivateKeyApiJob(account, e2eeBaseUrl(account) + "private-key", this);
@@ -1679,7 +2264,7 @@ void ClientSideEncryption::decryptPrivateKey(const AccountPtr &account, const QB
         if (ok) {
             prev = dialog.textValue();
 
-            _mnemonic = prev;
+            setMnemonic(prev);
             QString mnemonic = prev.split(" ").join(QString()).toLower();
 
             // split off salt
@@ -1691,17 +2276,17 @@ void ClientSideEncryption::decryptPrivateKey(const AccountPtr &account, const QB
 
             const auto privateKey = EncryptionHelper::decryptPrivateKey(password, key);
             if (!privateKey.isEmpty()) {
-                _privateKey = privateKey;
+                _encryptionCertificate.setPrivateKeyData(privateKey);
             } else {
                 const auto deprecatedSha1PrivateKey = EncryptionHelper::decryptPrivateKey(deprecatedSha1Password, key);
                 if (!deprecatedSha1PrivateKey.isEmpty()) {
-                    _privateKey = deprecatedSha1PrivateKey;
+                    _encryptionCertificate.setPrivateKeyData(deprecatedSha1PrivateKey);
                 } else {
-                    _privateKey = EncryptionHelper::decryptPrivateKey(deprecatedPassword, key);
+                    _encryptionCertificate.setPrivateKeyData(EncryptionHelper::decryptPrivateKey(deprecatedPassword, key));
                 }
             }
 
-            if (!_privateKey.isNull() && checkPublicKeyValidity(account)) {
+            if (!getPrivateKey().isNull() && checkPublicKeyValidity(account)) {
                 writePrivateKey(account);
                 writeCertificate(account);
                 writeMnemonic(account, [] () {});
@@ -1743,8 +2328,7 @@ void ClientSideEncryption::getPublicKeyFromServer(const AccountPtr &account)
     connect(job, &JsonApiJob::jsonReceived, [this, account](const QJsonDocument& doc, int retCode) {
         if (retCode == 200) {
             QString publicKey = doc.object()["ocs"].toObject()["data"].toObject()["public-keys"].toObject()[account->davUser()].toString();
-            _certificate = QSslCertificate(publicKey.toLocal8Bit(), QSsl::Pem);
-            _publicKey = _certificate.publicKey();
+            _encryptionCertificate = CertificateInformation{_encryptionCertificate.getPrivateKeyData(), QSslCertificate{publicKey.toLocal8Bit(), QSsl::Pem}};
             fetchAndValidatePublicKeyFromServer(account);
         } else if (retCode == 404) {
             qCDebug(lcCse()) << "No public key on the server";
@@ -1769,7 +2353,7 @@ void ClientSideEncryption::fetchAndValidatePublicKeyFromServer(const AccountPtr
         if (retCode == 200) {
             const auto serverPublicKey = doc.object()["ocs"].toObject()["data"].toObject()["public-key"].toString().toLatin1();
             if (checkServerPublicKeyValidity(serverPublicKey)) {
-                if (_privateKey.isEmpty()) {
+                if (getPrivateKey().isEmpty()) {
                     getPrivateKeyFromServer(account);
                 } else {
                     encryptPrivateKey(account);
@@ -2411,4 +2995,217 @@ OCC::NextcloudSslCertificate::operator QSslCertificate() const
     return _certificate;
 }
 
+CertificateInformation::CertificateInformation()
+{
+    checkEncryptionCertificate();
+}
+
+CertificateInformation::CertificateInformation(PKCS11_KEY *hardwarePrivateKey,
+                                               QSslCertificate &&certificate)
+    : _hardwarePrivateKey(hardwarePrivateKey)
+    , _certificate(std::move(certificate))
+{
+    checkEncryptionCertificate();
+}
+
+CertificateInformation::CertificateInformation(const QByteArray &privateKey, QSslCertificate &&certificate)
+    : _hardwarePrivateKey()
+    , _privateKeyData()
+    , _certificate(std::move(certificate))
+{
+    if (!privateKey.isEmpty()) {
+        setPrivateKeyData(privateKey);
+    }
+
+    checkEncryptionCertificate();
+}
+
+bool CertificateInformation::operator==(const CertificateInformation &other) const
+{
+    return _certificate.digest(QCryptographicHash::Sha256) == other._certificate.digest(QCryptographicHash::Sha256);
+}
+
+void CertificateInformation::clear()
+{
+    _hardwarePrivateKey = nullptr;
+    _privateKeyData.clear();
+    _certificate.clear();
+    _certificateExpired = true;
+    _certificateNotYetValid = true;
+    _certificateRevoked = true;
+    _certificateInvalid = true;
+}
+
+const QByteArray& CertificateInformation::getPrivateKeyData() const
+{
+    return _privateKeyData;
+}
+
+void CertificateInformation::setPrivateKeyData(const QByteArray &privateKey)
+{
+    _privateKeyData = privateKey;
+}
+
+QList<QSslError> CertificateInformation::verify() const
+{
+    auto result = QSslCertificate::verify({_certificate});
+
+    auto hasNeededExtendedKeyUsageExtension = false;
+    for (const auto &oneExtension : _certificate.extensions()) {
+        if (oneExtension.oid() == QStringLiteral("2.5.29.37")) {
+            const auto extendedKeyUsageList = oneExtension.value().toList();
+            for (const auto &oneExtendedKeyUsageValue : extendedKeyUsageList) {
+                if (oneExtendedKeyUsageValue == QStringLiteral("E-mail Protection")) {
+                    hasNeededExtendedKeyUsageExtension = true;
+                    break;
+                }
+            }
+        }
+    }
+    if (!hasNeededExtendedKeyUsageExtension) {
+        result.append(QSslError{QSslError::InvalidPurpose});
+    }
+
+    return result;
+}
+
+bool CertificateInformation::isSelfSigned() const
+{
+    return _certificate.isSelfSigned();
+}
+
+QSslKey CertificateInformation::getSslPublicKey() const
+{
+    return _certificate.publicKey();
+}
+
+PKey CertificateInformation::getEvpPublicKey() const
+{
+    const auto publicKey = _certificate.publicKey();
+    Q_ASSERT(!publicKey.isNull());
+    if (publicKey.isNull()) {
+        qCDebug(lcCse) << "Public key is null. Could not encrypt.";
+    }
+    Bio publicKeyBio;
+    const auto publicKeyPem = publicKey.toPem();
+    BIO_write(publicKeyBio, publicKeyPem.constData(), publicKeyPem.size());
+    return PKey::readPublicKey(publicKeyBio);
+}
+
+PKCS11_KEY *CertificateInformation::getPkcs11PrivateKey() const
+{
+    return canDecrypt() ? _hardwarePrivateKey : nullptr;
+}
+
+PKey CertificateInformation::getEvpPrivateKey() const
+{
+    if (_hardwarePrivateKey) {
+        return PKey::readHardwarePrivateKey(_hardwarePrivateKey);
+    } else {
+        const auto privateKeyPem = _privateKeyData;
+        Q_ASSERT(!privateKeyPem.isEmpty());
+        if (privateKeyPem.isEmpty()) {
+            qCDebug(lcCse) << "Private key is empty. Could not encrypt.";
+        }
+
+        Bio privateKeyBio;
+        BIO_write(privateKeyBio, privateKeyPem.constData(), privateKeyPem.size());
+        return PKey::readPrivateKey(privateKeyBio);
+    }
+}
+
+const QSslCertificate &CertificateInformation::getCertificate() const
+{
+    return _certificate;
+}
+
+bool CertificateInformation::canEncrypt() const
+{
+    return (_hardwarePrivateKey || !_certificate.isNull()) && !_certificateExpired && !_certificateNotYetValid && !_certificateRevoked && !_certificateInvalid;
+}
+
+bool CertificateInformation::canDecrypt() const
+{
+    return _hardwarePrivateKey || !_privateKeyData.isEmpty();
+}
+
+bool CertificateInformation::userCertificateNeedsMigration() const
+{
+    return _hardwarePrivateKey &&
+        (_certificateExpired || _certificateNotYetValid || _certificateRevoked || _certificateInvalid);
+}
+
+bool CertificateInformation::sensitiveDataRemaining() const
+{
+    return _hardwarePrivateKey && !_privateKeyData.isEmpty() && !_certificate.isNull();
+}
+
+QByteArray CertificateInformation::sha256Fingerprint() const
+{
+    return _certificate.digest(QCryptographicHash::Sha256).toBase64();
+}
+
+void CertificateInformation::checkEncryptionCertificate()
+{
+    _certificateExpired = false;
+    _certificateNotYetValid = false;
+    _certificateRevoked = false;
+    _certificateInvalid = false;
+
+    const auto sslErrors = QSslCertificate::verify({_certificate});
+    for (const auto &sslError : sslErrors) {
+        qCDebug(lcCse()) << "certificate validation error" << sslError;
+        switch (sslError.error())
+        {
+        case QSslError::CertificateExpired:
+            _certificateExpired = true;
+            break;
+        case QSslError::CertificateNotYetValid:
+            _certificateNotYetValid = true;
+            break;
+        case QSslError::CertificateRevoked:
+            _certificateRevoked = true;
+            break;
+        case QSslError::UnableToGetIssuerCertificate:
+        case QSslError::UnableToDecryptCertificateSignature:
+        case QSslError::UnableToDecodeIssuerPublicKey:
+        case QSslError::CertificateSignatureFailed:
+        case QSslError::InvalidNotBeforeField:
+        case QSslError::InvalidNotAfterField:
+        case QSslError::SelfSignedCertificate:
+        case QSslError::SelfSignedCertificateInChain:
+        case QSslError::UnableToGetLocalIssuerCertificate:
+        case QSslError::UnableToVerifyFirstCertificate:
+        case QSslError::InvalidCaCertificate:
+        case QSslError::PathLengthExceeded:
+        case QSslError::InvalidPurpose:
+        case QSslError::CertificateUntrusted:
+        case QSslError::CertificateRejected:
+        case QSslError::SubjectIssuerMismatch:
+        case QSslError::AuthorityIssuerSerialNumberMismatch:
+        case QSslError::NoPeerCertificate:
+        case QSslError::HostNameMismatch:
+        case QSslError::NoSslSupport:
+        case QSslError::CertificateBlacklisted:
+        case QSslError::CertificateStatusUnknown:
+        case QSslError::OcspNoResponseFound:
+        case QSslError::OcspMalformedRequest:
+        case QSslError::OcspMalformedResponse:
+        case QSslError::OcspInternalError:
+        case QSslError::OcspTryLater:
+        case QSslError::OcspSigRequred:
+        case QSslError::OcspUnauthorized:
+        case QSslError::OcspResponseCannotBeTrusted:
+        case QSslError::OcspResponseCertIdUnknown:
+        case QSslError::OcspResponseExpired:
+        case QSslError::OcspStatusUnknown:
+        case QSslError::UnspecifiedError:
+            _certificateInvalid = true;
+            break;
+        case QSslError::NoError:
+            break;
+        }
+    }
+}
+
 }
index 936b144423a9ba2873afc45ac85b5aae2c303a2c..0c09666898385786b2fa8ed25b5c3f88c76fed07 100644 (file)
@@ -1,3 +1,17 @@
+/*
+ * Copyright Â© 2017, Tomaz Canabrava <tcanabrava@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
 #ifndef CLIENTSIDEENCRYPTION_H
 #define CLIENTSIDEENCRYPTION_H
 
@@ -5,6 +19,8 @@
 
 #include "clientsideencryptionprimitives.h"
 #include "accountfwd.h"
+#include "networkjobs.h"
+#include "clientsideencryptiontokenselector.h"
 
 #include <QString>
 #include <QObject>
 #include <QMap>
 #include <QHash>
 
+#include <libp11.h>
+
 #include <openssl/evp.h>
 
+#include <functional>
+#include <optional>
+
+class QWidget;
+
 namespace QKeychain {
 class Job;
 class WritePasswordJob;
@@ -29,48 +52,109 @@ namespace OCC {
 
 QString e2eeBaseUrl(const OCC::AccountPtr &account);
 
+class ClientSideEncryption;
+
+class CertificateInformation {
+public:
+    CertificateInformation();
+
+    explicit CertificateInformation(PKCS11_KEY *hardwarePrivateKey,
+                                    QSslCertificate &&certificate);
+
+    explicit CertificateInformation(const QByteArray& privateKey,
+                                    QSslCertificate &&certificate);
+
+    [[nodiscard]] bool operator==(const CertificateInformation &other) const;
+
+    void clear();
+
+    [[nodiscard]] const QByteArray& getPrivateKeyData() const;
+
+    void setPrivateKeyData(const QByteArray& privateKey);
+
+    [[nodiscard]] QList<QSslError> verify() const;
+
+    [[nodiscard]] bool isSelfSigned() const;
+
+    [[nodiscard]] QSslKey getSslPublicKey() const;
+
+    [[nodiscard]] PKey getEvpPublicKey() const;
+
+    [[nodiscard]] PKCS11_KEY* getPkcs11PrivateKey() const;
+
+    [[nodiscard]] PKey getEvpPrivateKey() const;
+
+    [[nodiscard]] const QSslCertificate& getCertificate() const;
+
+    [[nodiscard]] bool canEncrypt() const;
+
+    [[nodiscard]] bool canDecrypt() const;
+
+    [[nodiscard]] bool userCertificateNeedsMigration() const;
+
+    [[nodiscard]] bool sensitiveDataRemaining() const;
+
+    [[nodiscard]] QByteArray sha256Fingerprint() const;
+
+private:
+    void checkEncryptionCertificate();
+
+    PKCS11_KEY* _hardwarePrivateKey = nullptr;
+
+    QByteArray _privateKeyData;
+
+    QSslCertificate _certificate;
+
+    bool _certificateExpired = true;
+
+    bool _certificateNotYetValid = true;
+
+    bool _certificateRevoked = true;
+
+    bool _certificateInvalid = true;
+};
+
 namespace EncryptionHelper {
-    OWNCLOUDSYNC_EXPORT QByteArray generateRandomFilename();
-    OWNCLOUDSYNC_EXPORT QByteArray generateRandom(int size);
-    QByteArray generatePassword(const QString &wordlist, const QByteArray& salt);
-    OWNCLOUDSYNC_EXPORT QByteArray encryptPrivateKey(
-            const QByteArray& key,
-            const QByteArray& privateKey,
-            const QByteArray &salt
-    );
-    OWNCLOUDSYNC_EXPORT QByteArray decryptPrivateKey(
-            const QByteArray& key,
-            const QByteArray& data
-    );
-    OWNCLOUDSYNC_EXPORT QByteArray extractPrivateKeySalt(const QByteArray &data);
-    OWNCLOUDSYNC_EXPORT QByteArray encryptStringSymmetric(
-            const QByteArray& key,
-            const QByteArray& data
-    );
-    OWNCLOUDSYNC_EXPORT QByteArray decryptStringSymmetric(
-            const QByteArray& key,
-            const QByteArray& data
-    );
-    OWNCLOUDSYNC_EXPORT QByteArray encryptStringAsymmetric(const QSslKey key, const QByteArray &data);
-    OWNCLOUDSYNC_EXPORT QByteArray decryptStringAsymmetric(const QByteArray &privateKeyPem, const QByteArray &data);
-
-    QByteArray privateKeyToPem(const QByteArray key);
-
-    //TODO: change those two EVP_PKEY into QSslKey.
-    QByteArray encryptStringAsymmetric(
-            EVP_PKEY *publicKey,
-            const QByteArray& data
-    );
-    QByteArray decryptStringAsymmetric(
-            EVP_PKEY *privateKey,
-            const QByteArray& data
-    );
-
-    OWNCLOUDSYNC_EXPORT bool fileEncryption(const QByteArray &key, const QByteArray &iv,
-                      QFile *input, QFile *output, QByteArray& returnTag);
-
-    OWNCLOUDSYNC_EXPORT bool fileDecryption(const QByteArray &key, const QByteArray &iv,
-                               QFile *input, QFile *output);
+
+OWNCLOUDSYNC_EXPORT QByteArray generateRandomFilename();
+OWNCLOUDSYNC_EXPORT QByteArray generateRandom(int size);
+QByteArray generatePassword(const QString &wordlist, const QByteArray& salt);
+OWNCLOUDSYNC_EXPORT QByteArray encryptPrivateKey(
+        const QByteArray& key,
+        const QByteArray& privateKey,
+        const QByteArray &salt
+);
+OWNCLOUDSYNC_EXPORT QByteArray decryptPrivateKey(
+        const QByteArray& key,
+        const QByteArray& data
+);
+OWNCLOUDSYNC_EXPORT QByteArray extractPrivateKeySalt(const QByteArray &data);
+OWNCLOUDSYNC_EXPORT QByteArray encryptStringSymmetric(
+        const QByteArray& key,
+        const QByteArray& data
+);
+OWNCLOUDSYNC_EXPORT QByteArray decryptStringSymmetric(
+        const QByteArray& key,
+        const QByteArray& data
+);
+
+[[nodiscard]] OWNCLOUDSYNC_EXPORT std::optional<QByteArray> encryptStringAsymmetric(const CertificateInformation &selectedCertificate,
+                                                                                    int paddingMode,
+                                                                                    const ClientSideEncryption &encryptionEngine,
+                                                                                    const QByteArray &binaryData);
+
+[[nodiscard]] OWNCLOUDSYNC_EXPORT std::optional<QByteArray> decryptStringAsymmetric(const CertificateInformation &selectedCertificate,
+                                                                                    int paddingMode,
+                                                                                    const ClientSideEncryption &encryptionEngine,
+                                                                                    const QByteArray &base64Data);
+
+QByteArray privateKeyToPem(const QByteArray key);
+
+OWNCLOUDSYNC_EXPORT bool fileEncryption(const QByteArray &key, const QByteArray &iv,
+                  QFile *input, QFile *output, QByteArray& returnTag);
+
+OWNCLOUDSYNC_EXPORT bool fileDecryption(const QByteArray &key, const QByteArray &iv,
+                           QFile *input, QFile *output);
 
     OWNCLOUDSYNC_EXPORT bool dataEncryption(const QByteArray &key, const QByteArray &iv, const QByteArray &input, QByteArray &output, QByteArray &returnTag);
     OWNCLOUDSYNC_EXPORT bool dataDecryption(const QByteArray &key, const QByteArray &iv, const QByteArray &input, QByteArray &output);
@@ -148,14 +232,54 @@ private:
 
 class OWNCLOUDSYNC_EXPORT ClientSideEncryption : public QObject {
     Q_OBJECT
+
+    Q_PROPERTY(bool canEncrypt READ canEncrypt NOTIFY canEncryptChanged FINAL)
+    Q_PROPERTY(bool canDecrypt READ canDecrypt NOTIFY canDecryptChanged FINAL)
+    Q_PROPERTY(bool userCertificateNeedsMigration READ userCertificateNeedsMigration NOTIFY userCertificateNeedsMigrationChanged FINAL)
 public:
     ClientSideEncryption();
 
-    QByteArray _privateKey;
-    QSslKey _publicKey;
-    QSslCertificate _certificate;
-    QString _mnemonic;
-    bool _newMnemonicGenerated = false;
+    [[nodiscard]] bool isInitialized() const;
+
+    [[nodiscard]] bool tokenIsSetup() const;
+
+    [[nodiscard]] QSslKey getPublicKey() const;
+
+    [[nodiscard]] const QByteArray& getPrivateKey() const;
+
+    void setPrivateKey(const QByteArray &privateKey);
+
+    [[nodiscard]] const CertificateInformation& getCertificateInformation() const;
+
+    [[nodiscard]] CertificateInformation getCertificateInformationByFingerprint(const QByteArray &certificateFingerprint) const;
+
+    [[nodiscard]] int paddingMode() const;
+
+    [[nodiscard]] CertificateInformation getTokenCertificateByFingerprint(const QByteArray &expectedFingerprint) const;
+
+    [[nodiscard]] bool useTokenBasedEncryption() const;
+
+    [[nodiscard]] const QString &getMnemonic() const;
+
+    void setCertificate(const QSslCertificate &certificate);
+
+    [[nodiscard]] const QSslCertificate& getCertificate() const;
+
+    [[nodiscard]] ENGINE* sslEngine() const;
+
+    [[nodiscard]] QByteArray generateSignatureCryptographicMessageSyntax(const QByteArray &data) const;
+
+    [[nodiscard]] bool verifySignatureCryptographicMessageSyntax(const QByteArray &cmsContent, const QByteArray &data, const QVector<QByteArray> &certificatePems) const;
+
+    [[nodiscard]] ClientSideEncryptionTokenSelector* usbTokenInformation();
+
+    [[nodiscard]] bool canEncrypt() const;
+
+    [[nodiscard]] bool canDecrypt() const;
+
+    [[nodiscard]] bool userCertificateNeedsMigration() const;
+
+    [[nodiscard]] QByteArray certificateSha256Fingerprint() const;
 
 signals:
     void initializationFinished(bool isNewMnemonicGenerated = false);
@@ -167,18 +291,28 @@ signals:
     void certificateFetchedFromKeychain(QSslCertificate certificate);
     void certificatesFetchedFromServer(const QHash<QString, OCC::NextcloudSslCertificate> &results);
     void certificateWriteComplete(const QSslCertificate &certificate);
+    void displayTokenInitDialog();
 
-public:
-    [[nodiscard]] QByteArray generateSignatureCryptographicMessageSyntax(const QByteArray &data) const;
-    [[nodiscard]] bool verifySignatureCryptographicMessageSyntax(const QByteArray &cmsContent, const QByteArray &data, const QVector<QByteArray> &certificatePems) const;
+    void startingDiscoveryEncryptionUsbToken();
+    void finishedDiscoveryEncryptionUsbToken();
+
+    void canEncryptChanged();
+    void canDecryptChanged();
+    void userCertificateNeedsMigrationChanged();
 
 public slots:
-    void initialize(const OCC::AccountPtr &account);
+    void initialize(QWidget *settingsDialog,
+                    const OCC::AccountPtr &account);
+    void initializeHardwareTokenEncryption(QWidget* settingsDialog,
+                                           const OCC::AccountPtr &account);
+    void addExtraRootCertificates();
     void forgetSensitiveData(const OCC::AccountPtr &account);
     void getUsersPublicKeyFromServer(const OCC::AccountPtr &account, const QStringList &userIds);
     void fetchCertificateFromKeyChain(const OCC::AccountPtr &account, const QString &userId);
     void writeCertificate(const OCC::AccountPtr &account, const QString &userId, const QSslCertificate &certificate);
 
+    void migrateCertificate();
+
 private slots:
     void generateKeyPair(const OCC::AccountPtr &account);
     void encryptPrivateKey(const OCC::AccountPtr &account);
@@ -205,9 +339,16 @@ private slots:
     void writePrivateKey(const OCC::AccountPtr &account);
     void writeCertificate(const OCC::AccountPtr &account);
 
+    void completeHardwareTokenInitialization(QWidget *settingsDialog,
+                                             const OCC::AccountPtr &account);
+
+    void setMnemonic(const QString &mnemonic);
+
 private:
     void generateMnemonic();
 
+    void setEncryptionCertificate(CertificateInformation certificateInfo);
+
     [[nodiscard]] std::pair<QByteArray, PKey> generateCSR(const AccountPtr &account,
                                                           PKey keyPair,
                                                           PKey privateKey);
@@ -216,6 +357,8 @@ private:
                             PKey keyPair,
                             const QByteArray &csrContent);
 
+    void sendPublicKey(const AccountPtr &account);
+
     void writeKeyPair(const AccountPtr &account,
                       PKey keyPair,
                       const QByteArray &csrContent);
@@ -246,9 +389,27 @@ private:
     [[nodiscard]] bool checkServerPublicKeyValidity(const QByteArray &serverPublicKeyString) const;
     [[nodiscard]] bool sensitiveDataRemaining() const;
 
+    [[nodiscard]] bool checkEncryptionIsWorking() const;
+
     void failedToInitialize(const AccountPtr &account);
 
-    bool isInitialized = false;
+    void saveCertificateIdentification(const AccountPtr &account) const;
+    void cacheTokenPin(const QString pin);
+
+    QString _mnemonic;
+    bool _newMnemonicGenerated = false;
+
+    QString _cachedPin;
+
+    ClientSideEncryptionTokenSelector _usbTokenInformation;
+
+    CertificateInformation _encryptionCertificate;
+    std::vector<CertificateInformation> _otherCertificates;
+
+    Pkcs11Context _context{Pkcs11Context::State::EmptyContext};
+    std::unique_ptr<PKCS11_SLOT[], std::function<void(PKCS11_SLOT*)>> _tokenSlots;
 };
+
 } // namespace OCC
+
 #endif
index d39c393d1777ba202245666ee1e957c460a92b0b..ecd3242afac491239628628f0bbf0aad2edd7c85 100644 (file)
@@ -20,6 +20,7 @@
 #include "common/syncjournaldb.h"
 
 Q_LOGGING_CATEGORY(lcSignPublicKeyApiJob, "nextcloud.sync.networkjob.sendcsr", QtInfoMsg)
+Q_LOGGING_CATEGORY(lcStorePublicKeyApiJob, "nextcloud.sync.networkjob.storepublickey", QtInfoMsg)
 Q_LOGGING_CATEGORY(lcStorePrivateKeyApiJob, "nextcloud.sync.networkjob.storeprivatekey", QtInfoMsg)
 Q_LOGGING_CATEGORY(lcCseJob, "nextcloud.sync.networkjob.clientsideencrypt", QtInfoMsg)
 
@@ -77,16 +78,16 @@ bool GetMetadataApiJob::finished()
 }
 
 StoreMetaDataApiJob::StoreMetaDataApiJob(const AccountPtr& account,
-                                                 const QByteArray& fileId,
-                                                 const QByteArray &token,
-                                                 const QByteArray& b64Metadata,
-                                                 const QByteArray &signature,
-                                                 QObject* parent)
-: AbstractNetworkJob(account, e2eeBaseUrl(account) + QStringLiteral("meta-data/") + fileId, parent),
-_fileId(fileId),
-_token(token),
-_b64Metadata(b64Metadata),
-_signature(signature)
+                                         const QByteArray& fileId,
+                                         const QByteArray &token,
+                                         const QByteArray& b64Metadata,
+                                         const QByteArray &signature,
+                                         QObject* parent)
+    : AbstractNetworkJob(account, e2eeBaseUrl(account) + QStringLiteral("meta-data/") + fileId, parent),
+    _fileId(fileId),
+    _token(token),
+    _b64Metadata(b64Metadata),
+    _signature(signature)
 {
 }
 
@@ -98,6 +99,8 @@ void StoreMetaDataApiJob::start()
     if (_account->capabilities().clientSideEncryptionVersion() >= 2.0) {
         if (!_signature.isEmpty()) {
             req.setRawHeader(e2eeSignatureHeaderName, _signature);
+        } else {
+            qCWarning(lcCseJob()) << "empty signature for" << _fileId;
         }
     }
     QUrlQuery query;
@@ -121,14 +124,16 @@ void StoreMetaDataApiJob::start()
 
 bool StoreMetaDataApiJob::finished()
 {
-    int retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-               if (retCode != 200) {
-                       qCInfo(lcCseJob()) << "error sending the metadata" << path() << errorString() << retCode;
-                       emit error(_fileId, retCode);
-            return false;
-               }
-               qCInfo(lcCseJob()) << "Metadata submitted to the server successfully";
-               emit success(_fileId);
+    const auto retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+    if (retCode != 200) {
+        qCInfo(lcCseJob()) << "error sending the metadata" << path() << errorString() << retCode;
+        emit error(_fileId, retCode);
+        return false;
+    }
+
+    qCInfo(lcCseJob()) << "Metadata submitted to the server successfully";
+    emit success(_fileId);
+
     return true;
 }
 
@@ -299,14 +304,16 @@ bool DeleteMetadataApiJob::finished()
 
 LockEncryptFolderApiJob::LockEncryptFolderApiJob(const AccountPtr &account,
                                                  const QByteArray &fileId,
+                                                 const QByteArray &certificateSha256Fingerprint,
                                                  SyncJournalDb *journalDb,
-                                                 const QSslKey publicKey,
+                                                 const QSslKey &sslkey,
                                                  QObject *parent)
     : AbstractNetworkJob(account, e2eeBaseUrl(account) + QStringLiteral("lock/") + fileId, parent)
     , _fileId(fileId)
+    , _certificateSha256Fingerprint(certificateSha256Fingerprint)
     , _journalDb(journalDb)
-    , _publicKey(publicKey)
 {
+    Q_UNUSED(sslkey)
 }
 
 void LockEncryptFolderApiJob::start()
@@ -315,8 +322,12 @@ void LockEncryptFolderApiJob::start()
 
     if (!folderTokenEncrypted.isEmpty()) {
         qCInfo(lcCseJob()) << "lock folder started for:" << path() << " for fileId: " << _fileId << " but we need to first lift the previous lock";
-        const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->_privateKey, folderTokenEncrypted);
-        const auto unlockJob = new OCC::UnlockEncryptFolderApiJob(_account, _fileId, folderToken, _journalDb, this);
+        const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getCertificateInformation(), _account->e2e()->paddingMode(), *_account->e2e(), folderTokenEncrypted);
+        if (!folderToken) {
+            qCWarning(lcCseJob()) << "decrypt failed";
+            return;
+        }
+        const auto unlockJob = new OCC::UnlockEncryptFolderApiJob(_account, _fileId, *folderToken, _journalDb, this);
         unlockJob->setShouldRollbackMetadataChanges(true);
         connect(unlockJob, &UnlockEncryptFolderApiJob::done, this, [this]() {
             this->start();
@@ -364,9 +375,13 @@ bool LockEncryptFolderApiJob::finished()
 
     qCInfo(lcCseJob()) << "lock folder finished with code" << retCode << " for:" << path() << " for fileId: " << _fileId << " token:" << token;
 
-    if (!_publicKey.isNull()) {
-        const auto folderTokenEncrypted = EncryptionHelper::encryptStringAsymmetric(_publicKey, token);
-        _journalDb->setE2EeLockedFolder(_fileId, folderTokenEncrypted);
+    if (!_account->e2e()->getPublicKey().isNull()) {
+        const auto folderTokenEncrypted = EncryptionHelper::encryptStringAsymmetric(_account->e2e()->getCertificateInformation(), _account->e2e()->paddingMode(), *_account->e2e(), token);
+        if (!folderTokenEncrypted) {
+            qCWarning(lcCseJob()) << "decrypt failed";
+            return false;
+        }
+        _journalDb->setE2EeLockedFolder(_fileId, *folderTokenEncrypted);
     }
 
     //TODO: Parse the token and submit.
@@ -410,6 +425,45 @@ bool SetEncryptionFlagApiJob::finished()
     return true;
 }
 
+StorePublicKeyApiJob::StorePublicKeyApiJob(const AccountPtr& account, const QString& path, QObject* parent)
+    : AbstractNetworkJob(account, path, parent)
+{
+}
+
+void StorePublicKeyApiJob::setPublicKey(const QByteArray& publicKey)
+{
+    QByteArray data = "publicKey=";
+    data += QUrl::toPercentEncoding(publicKey);
+    _publicKey.setData(data);
+}
+
+void StorePublicKeyApiJob::start()
+{
+    QNetworkRequest req;
+    req.setRawHeader("OCS-APIREQUEST", "true");
+    req.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/x-www-form-urlencoded"));
+    QUrlQuery query;
+    query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
+    QUrl url = Utility::concatUrlPath(account()->url(), path());
+    url.setQuery(query);
+
+    qCDebug(lcStorePublicKeyApiJob) << "Sending the public key";
+    sendRequest("PUT", url, req, &_publicKey);
+    AbstractNetworkJob::start();
+}
+
+bool StorePublicKeyApiJob::finished()
+{
+    int retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+    if (retCode != 200)
+        qCInfo(lcStorePublicKeyApiJob()) << "Sending public key ended with"  << path() << errorString() << retCode;
+
+    QJsonParseError error{};
+    auto json = QJsonDocument::fromJson(reply()->readAll(), &error);
+    emit jsonReceived(json, reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
+    return true;
+}
+
 StorePrivateKeyApiJob::StorePrivateKeyApiJob(const AccountPtr& account, const QString& path, QObject* parent)
 : AbstractNetworkJob(account, path, parent)
 {
@@ -431,7 +485,7 @@ void StorePrivateKeyApiJob::start()
     QUrl url = Utility::concatUrlPath(account()->url(), path());
     url.setQuery(query);
 
-    qCInfo(lcStorePrivateKeyApiJob) << "Sending the private key" << _privKey.data();
+    qCDebug(lcStorePrivateKeyApiJob) << "Sending the private key";
     sendRequest("POST", url, req, &_privKey);
     AbstractNetworkJob::start();
 }
@@ -470,7 +524,7 @@ void SignPublicKeyApiJob::start()
     QUrl url = Utility::concatUrlPath(account()->url(), path());
     url.setQuery(query);
 
-    qCInfo(lcSignPublicKeyApiJob) << "Sending the CSR" << _csr.data();
+    qCDebug(lcSignPublicKeyApiJob) << "Sending the CSR";
     sendRequest("POST", url, req, &_csr);
     AbstractNetworkJob::start();
 }
index 9052e9bbcb420e13dcbf2584efafe39744abe0ae..6c1f5de91e9257ec33e8bfcbc37443b5f81f53ee 100644 (file)
@@ -3,9 +3,10 @@
 
 #include "networkjobs.h"
 #include "accountfwd.h"
+
+#include <QSslKey>
 #include <QString>
 #include <QJsonDocument>
-#include <QSslKey>
 
 namespace OCC {
 /* Here are all of the network jobs for the client side encryption.
@@ -57,6 +58,49 @@ private:
     QBuffer _csr;
 };
 
+/*
+ * @brief Job to upload the PublicKey that return JSON
+ *
+ * To be used like this:
+ * \code
+ * _job = new StorePublicKeyApiJob(account, QLatin1String("ocs/v1.php/foo/bar"), this);
+ * _job->setPublicKey( privKey );
+ * connect(_job...);
+ * _job->start();
+ * \encode
+ *
+ * @ingroup libsync
+ */
+class OWNCLOUDSYNC_EXPORT StorePublicKeyApiJob : public AbstractNetworkJob
+{
+    Q_OBJECT
+public:
+    explicit StorePublicKeyApiJob(const AccountPtr &account, const QString &path, QObject *parent = nullptr);
+
+    /**
+     * @brief setCsr - the CSR with the public key.
+     * This function needs to be called before start() obviously.
+     */
+    void setPublicKey(const QByteArray& publicKey);
+
+public slots:
+    void start() override;
+
+protected:
+    bool finished() override;
+signals:
+
+    /**
+     * @brief jsonReceived - signal to report the json answer from ocs
+     * @param json - the parsed json document
+     * @param statusCode - the OCS status code: 100 (!) for success
+     */
+    void jsonReceived(const QJsonDocument &json, int statusCode);
+
+private:
+    QBuffer _publicKey;
+};
+
 /*
  * @brief Job to upload the PrivateKey that return JSON
  *
@@ -145,7 +189,12 @@ class OWNCLOUDSYNC_EXPORT LockEncryptFolderApiJob : public AbstractNetworkJob
 {
     Q_OBJECT
 public:
-    explicit LockEncryptFolderApiJob(const AccountPtr &account, const QByteArray &fileId, SyncJournalDb *journalDb, const QSslKey publicKey, QObject *parent = nullptr);
+    explicit LockEncryptFolderApiJob(const AccountPtr &account,
+                                     const QByteArray &fileId,
+                                     const QByteArray &certificateSha256Fingerprint,
+                                     SyncJournalDb *journalDb,
+                                     const QSslKey &sslkey,
+                                     QObject *parent = nullptr);
 
     void setCounter(const quint64 counter);
 
@@ -163,6 +212,7 @@ signals:
 
 private:
     QByteArray _fileId;
+    QByteArray _certificateSha256Fingerprint;
     QPointer<SyncJournalDb> _journalDb;
     QSslKey _publicKey;
     quint64 _counter = 0;
index 92210df5fc1ef5d0f444f1bb09ad025549094cdd..6db6d1a53514d7ec80d2c949f50e2036050af92f 100644 (file)
  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  * for more details.
  */
+
 #include "clientsideencryptionprimitives.h"
+
+#include <QLoggingCategory>
+
 #include <openssl/pem.h>
 
 namespace OCC
 {
-Bio::Bio()
-    : _bio(BIO_new(BIO_s_mem()))
-{
-}
-Bio::~Bio()
-{
-    BIO_free_all(_bio);
-}
+
+Q_LOGGING_CATEGORY(lcCseUtility, "nextcloud.sync.clientsideencryption.utility", QtInfoMsg)
+
 Bio::operator const BIO *() const
 {
     return _bio;
@@ -53,6 +52,7 @@ PKeyCtx PKeyCtx::forKey(EVP_PKEY *pkey, ENGINE *e)
 {
     PKeyCtx ctx;
     ctx._ctx = EVP_PKEY_CTX_new(pkey, e);
+    Q_ASSERT(ctx._ctx);
     return ctx;
 }
 
@@ -78,6 +78,13 @@ PKey PKey::readPublicKey(Bio &bio)
     return result;
 }
 
+PKey PKey::readHardwarePublicKey(PKCS11_KEY *key)
+{
+    PKey result;
+    result._pkey = PKCS11_get_public_key(key);
+    return result;
+}
+
 PKey PKey::readPrivateKey(Bio &bio)
 {
     PKey result;
@@ -85,6 +92,13 @@ PKey PKey::readPrivateKey(Bio &bio)
     return result;
 }
 
+PKey PKey::readHardwarePrivateKey(PKCS11_KEY *key)
+{
+    PKey result;
+    result._pkey = PKCS11_get_private_key(key);
+    return result;
+}
+
 PKey PKey::generate(PKeyCtx &ctx)
 {
     PKey result;
@@ -104,4 +118,44 @@ PKey::operator EVP_PKEY *() const
     return _pkey;
 }
 
-}
\ No newline at end of file
+Pkcs11Context::Pkcs11Context(State initState)
+    : _pkcsS11Ctx(initState == State::CreateContext ? PKCS11_CTX_new() : nullptr)
+{
+}
+
+Pkcs11Context::Pkcs11Context(Pkcs11Context &&otherContext)
+    : _pkcsS11Ctx(otherContext._pkcsS11Ctx)
+{
+    otherContext._pkcsS11Ctx = nullptr;
+}
+
+Pkcs11Context::~Pkcs11Context()
+{
+    if (_pkcsS11Ctx) {
+        PKCS11_CTX_free(_pkcsS11Ctx);
+        _pkcsS11Ctx = nullptr;
+    }
+}
+
+Pkcs11Context &Pkcs11Context::operator=(Pkcs11Context &&otherContext)
+{
+    if (&otherContext != this) {
+        if (_pkcsS11Ctx) {
+            PKCS11_CTX_free(_pkcsS11Ctx);
+            _pkcsS11Ctx = nullptr;
+        }
+        std::swap(_pkcsS11Ctx, otherContext._pkcsS11Ctx);
+    }
+
+    return *this;
+}
+
+void Pkcs11Context::clear()
+{
+    if (_pkcsS11Ctx) {
+        PKCS11_CTX_free(_pkcsS11Ctx);
+        _pkcsS11Ctx = nullptr;
+    }
+}
+
+}
index 28efc9b29c3df17d944357470903d21c6ecbd1c9..6d1bce28ea1cdcc36c80814c7cd2161080b1e308 100644 (file)
  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  * for more details.
  */
+
 #pragma once
-#include <openssl/evp.h>
+
 #include <QtCore/qglobal.h>
 
+#include <openssl/evp.h>
+#include <libp11.h>
+
 namespace OCC
 {
 class Bio
 {
 public:
-    Bio();
+    Bio()
+        : _bio(BIO_new(BIO_s_mem()))
+    {
+    }
 
-    ~Bio();
+    ~Bio()
+    {
+        BIO_free_all(_bio);
+    }
 
     operator const BIO *() const;
     operator BIO *();
@@ -74,8 +84,12 @@ public:
 
     static PKey readPublicKey(Bio &bio);
 
+    static PKey readHardwarePublicKey(PKCS11_KEY *key);
+
     static PKey readPrivateKey(Bio &bio);
 
+    static PKey readHardwarePrivateKey(PKCS11_KEY *key);
+
     static PKey generate(PKeyCtx &ctx);
 
     operator EVP_PKEY *();
@@ -89,4 +103,40 @@ private:
 
     EVP_PKEY *_pkey = nullptr;
 };
-}
\ No newline at end of file
+
+class Pkcs11Context {
+public:
+    enum class State {
+        CreateContext,
+        EmptyContext,
+    };
+
+    explicit Pkcs11Context(State initState);
+
+    Pkcs11Context(Pkcs11Context &&otherContext);
+
+    Pkcs11Context(const Pkcs11Context&) = delete;
+
+    ~Pkcs11Context();
+
+    Pkcs11Context& operator=(Pkcs11Context &&otherContext);
+
+    Pkcs11Context& operator=(const Pkcs11Context&) = delete;
+
+    operator const PKCS11_CTX*() const
+    {
+        return _pkcsS11Ctx;
+    }
+
+    operator PKCS11_CTX*()
+    {
+        return _pkcsS11Ctx;
+    }
+
+    void clear();
+
+private:
+    PKCS11_CTX* _pkcsS11Ctx = nullptr;
+};
+
+}
diff --git a/src/libsync/clientsideencryptiontokenselector.cpp b/src/libsync/clientsideencryptiontokenselector.cpp
new file mode 100644 (file)
index 0000000..9398345
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ * Copyright Â© 2023, Matthieu Gallien <matthieu.gallien@nextcloud.com>
+ * Copyright (C) 2017 The Qt Company Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Commercial License Usage
+ * Licensees holding valid commercial Qt licenses may use this file in
+ * accordance with the commercial license agreement provided with the
+ * Software or, alternatively, in accordance with the terms contained in
+ * a written agreement between you and The Qt Company. For licensing terms
+ * and conditions see https://www.qt.io/terms-conditions. For further
+ * information use the contact form at https://www.qt.io/contact-us.
+ *
+ * GNU Lesser General Public License Usage
+ * Alternatively, this file may be used under the terms of the GNU Lesser
+ * General Public License version 3 as published by the Free Software
+ * Foundation and appearing in the file LICENSE.LGPL3 included in the
+ * packaging of this file. Please review the following information to
+ * ensure the GNU Lesser General Public License version 3 requirements
+ * will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+ *
+ * GNU General Public License Usage
+ * Alternatively, this file may be used under the terms of the GNU
+ * General Public License version 2.0 or (at your option) the GNU General
+ * Public license version 3 or any later version approved by the KDE Free
+ * Qt Foundation. The licenses are as published by the Free Software
+ * Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+ * included in the packaging of this file. Please review the following
+ * information to ensure the GNU General Public License requirements will
+ * be met: https://www.gnu.org/licenses/gpl-2.0.html and
+ * https://www.gnu.org/licenses/gpl-3.0.html.
+ */
+
+#include "clientsideencryptiontokenselector.h"
+
+#include "clientsideencryptionprimitives.h"
+#include "account.h"
+
+#include <QLoggingCategory>
+#include <QtConcurrentRun>
+#include <QSslCertificateExtension>
+
+#if defined(Q_OS_WIN)
+#include <wincrypt.h>
+#endif
+
+#include <libp11.h>
+
+#include <openssl/pem.h>
+
+namespace {
+
+static unsigned char* unsignedData(QByteArray& array)
+{
+    return (unsigned char*)array.data();
+}
+
+static QByteArray BIO2ByteArray(OCC::Bio &b) {
+    auto pending = static_cast<int>(BIO_ctrl_pending(b));
+    QByteArray res(pending, '\0');
+    BIO_read(b, unsignedData(res), pending);
+    return res;
+}
+
+}
+
+namespace OCC
+{
+
+Q_LOGGING_CATEGORY(lcCseSelector, "nextcloud.sync.clientsideencryption.selector", QtInfoMsg)
+
+ClientSideEncryptionTokenSelector::ClientSideEncryptionTokenSelector(QObject *parent)
+    : QObject{parent}
+{
+
+}
+
+bool ClientSideEncryptionTokenSelector::isSetup() const
+{
+    return !_sha256Fingerprint.isEmpty();
+}
+
+QVariantList ClientSideEncryptionTokenSelector::discoveredCertificates() const
+{
+    return _discoveredCertificates;
+}
+
+QByteArray ClientSideEncryptionTokenSelector::sha256Fingerprint() const
+{
+    return _sha256Fingerprint;
+}
+
+void ClientSideEncryptionTokenSelector::clear()
+{
+    _discoveredCertificates.clear();
+    _sha256Fingerprint.clear();
+}
+
+QFuture<void> ClientSideEncryptionTokenSelector::searchForCertificates(const AccountPtr &account)
+{
+    return QtConcurrent::run([this, account] () -> void {
+        discoverCertificates(account);
+    });
+}
+
+void ClientSideEncryptionTokenSelector::setSha256Fingerprint(const QByteArray &sha256Fingerprint)
+{
+    if (_sha256Fingerprint == sha256Fingerprint) {
+        return;
+    }
+
+    _sha256Fingerprint = sha256Fingerprint;
+    Q_EMIT sha256FingerprintChanged();
+}
+
+void ClientSideEncryptionTokenSelector::discoverCertificates(const AccountPtr &account)
+{
+    auto ctx = Pkcs11Context{Pkcs11Context::State::CreateContext};
+
+    auto rc = PKCS11_CTX_load(ctx, account->encryptionHardwareTokenDriverPath().toLatin1().constData());
+    if (rc) {
+        qCWarning(lcCseSelector()) << "loading pkcs11 engine failed:" << ERR_reason_error_string(ERR_get_error()) << account->encryptionHardwareTokenDriverPath();
+
+        Q_EMIT failedToInitialize(account);
+        return;
+    }
+
+    auto tokensCount = 0u;
+    PKCS11_SLOT *tempTokenSlots = nullptr;
+    /* get information on all slots */
+    if (PKCS11_enumerate_slots(ctx, &tempTokenSlots, &tokensCount) < 0) {
+        qCWarning(lcCseSelector()) << "no slots available" << ERR_reason_error_string(ERR_get_error());
+
+        Q_EMIT failedToInitialize(account);
+        return;
+    }
+
+    auto deleter = [&ctx, tokensCount] (PKCS11_SLOT* pointer) noexcept -> void {
+        PKCS11_release_all_slots(ctx, pointer, tokensCount);
+    };
+
+    auto tokenSlots = std::unique_ptr<PKCS11_SLOT[], decltype(deleter)>{tempTokenSlots, deleter};
+
+    if (!tokensCount) {
+        qCWarning(lcCseSelector()) << "no tokens found";
+
+        Q_EMIT failedToInitialize(account);
+        return;
+    }
+
+    _discoveredCertificates.clear();
+    auto currentSlot = static_cast<PKCS11_SLOT*>(nullptr);
+    for(auto tokenIndex = 0u; tokenIndex < tokensCount; ++tokenIndex) {
+        currentSlot = PKCS11_find_next_token(ctx, tokenSlots.get(), tokensCount, currentSlot);
+        if (currentSlot == nullptr || currentSlot->token == nullptr) {
+            break;
+        }
+
+        qCDebug(lcCseSelector()) << "Slot manufacturer......:" << currentSlot->manufacturer;
+        qCDebug(lcCseSelector()) << "Slot description.......:" << currentSlot->description;
+        qCDebug(lcCseSelector()) << "Slot token label.......:" << currentSlot->token->label;
+        qCDebug(lcCseSelector()) << "Slot token manufacturer:" << currentSlot->token->manufacturer;
+        qCDebug(lcCseSelector()) << "Slot token model.......:" << currentSlot->token->model;
+        qCDebug(lcCseSelector()) << "Slot token serialnr....:" << currentSlot->token->serialnr;
+
+        auto keysCount = 0u;
+        auto certificatesFromToken = static_cast<PKCS11_CERT*>(nullptr);
+        if (PKCS11_enumerate_certs(currentSlot->token, &certificatesFromToken, &keysCount)) {
+            qCWarning(lcCseSelector()) << "PKCS11_enumerate_certs failed" << ERR_reason_error_string(ERR_get_error());
+
+            Q_EMIT failedToInitialize(account);
+            return;
+        }
+
+        for (auto certificateIndex = 0u; certificateIndex < keysCount; ++certificateIndex) {
+            const auto currentCertificate = &certificatesFromToken[certificateIndex];
+            qCInfo(lcCseSelector()) << "certificate metadata:"
+                                    << "label:" << currentCertificate->label;
+
+            const auto certificateId = QByteArray{reinterpret_cast<char*>(currentCertificate->id), static_cast<int>(currentCertificate->id_len)};
+            qCInfo(lcCseSelector()) << "new certificate ID:" << certificateId.toBase64();
+
+            Bio out;
+            const auto ret = PEM_write_bio_X509(out, currentCertificate->x509);
+            if (ret <= 0){
+                qCWarning(lcCseSelector()) << "PEM_write_bio_X509 failed" << ERR_reason_error_string(ERR_get_error());
+
+                Q_EMIT failedToInitialize(account);
+                return;
+            }
+
+            const auto result = BIO2ByteArray(out);
+            const auto sslCertificate = QSslCertificate{result, QSsl::Pem};
+            const auto certificateDigest = sslCertificate.digest(QCryptographicHash::Sha256).toBase64();
+
+            qCInfo(lcCseSelector()) << "newly found certificate"
+                                    << "subject:" << sslCertificate.subjectDisplayName()
+                                    << "issuer:" << sslCertificate.issuerDisplayName()
+                                    << "valid since:" << sslCertificate.effectiveDate()
+                                    << "valid until:" << sslCertificate.expiryDate()
+                                    << "serial number:" << sslCertificate.serialNumber()
+                                    << "SHA256 fingerprint:" << certificateDigest;
+
+            if (sslCertificate.isSelfSigned()) {
+                qCDebug(lcCseSelector()) << "newly found certificate is self signed: goint to ignore it";
+                continue;
+            }
+
+            auto hasNeededExtendedKeyUsageExtension = false;
+            const auto &allExtensions = sslCertificate.extensions();
+            for (const auto &oneExtension : allExtensions) {
+                qCDebug(lcCseSelector()) << "extension:" << (oneExtension.isCritical() ? "is critical" : "") << (oneExtension.isSupported() ? "is supported" : "") << oneExtension.name() << oneExtension.value() << oneExtension.oid();
+                if (oneExtension.oid() == QStringLiteral("2.5.29.37")) {
+                    const auto extendedKeyUsageList = oneExtension.value().toList();
+                    for (const auto &oneExtendedKeyUsageValue : extendedKeyUsageList) {
+                        qCDebug(lcCseSelector()) << "EKU:" << oneExtendedKeyUsageValue;
+                        if (oneExtendedKeyUsageValue == QStringLiteral("E-mail Protection")) {
+                            hasNeededExtendedKeyUsageExtension = true;
+                            break;
+                        }
+                    }
+                }
+            }
+            if (!hasNeededExtendedKeyUsageExtension) {
+                qCDebug(lcCseSelector()) << "newly found certificate is missing the required EKU extension: Secure Email (1.3.6.1.5.5.7.3.4)";
+                continue;
+            }
+
+            _discoveredCertificates.push_back(QVariantMap{
+                                                          {QStringLiteral("label"), QString::fromLatin1(currentCertificate->label)},
+                                                          {QStringLiteral("subject"), sslCertificate.subjectDisplayName()},
+                                                          {QStringLiteral("issuer"), sslCertificate.issuerDisplayName()},
+                                                          {QStringLiteral("serialNumber"), sslCertificate.serialNumber()},
+                                                          {QStringLiteral("validSince"), sslCertificate.effectiveDate()},
+                                                          {QStringLiteral("validUntil"), sslCertificate.expiryDate()},
+                                                          {QStringLiteral("sha256Fingerprint"), certificateDigest},
+                                                          {QStringLiteral("certificate"), QVariant::fromValue(sslCertificate)},
+                                                          });
+
+            std::sort(_discoveredCertificates.begin(), _discoveredCertificates.end(), [] (const auto &first, const auto &second) -> bool {
+                return first.toMap()[QStringLiteral("validSince")].toDateTime() > second.toMap()[QStringLiteral("validSince")].toDateTime();
+            });
+        }
+    }
+
+    processDiscoveredCertificates();
+}
+
+void ClientSideEncryptionTokenSelector::processDiscoveredCertificates()
+{
+    const auto &allCertificates = discoveredCertificates();
+    for (const auto &oneCertificate : allCertificates) {
+        const auto certificateData = oneCertificate.toMap();
+        const auto sslCertificate = certificateData[QStringLiteral("certificate")].value<QSslCertificate>();
+        if (sslCertificate.isNull()) {
+            qCDebug(lcCseSelector()) << "null certificate";
+            continue;
+        }
+        const auto sslErrors = QSslCertificate::verify({sslCertificate});
+        if (!sslErrors.isEmpty()) {
+            for (const auto &oneError : sslErrors) {
+                qCInfo(lcCseSelector()) << oneError;
+            }
+            continue;
+        }
+
+        const auto &sha256Fingerprint = sslCertificate.digest(QCryptographicHash::Sha256).toBase64();
+        qCInfo(lcCseSelector()) << "selected certificate" << certificateData[QStringLiteral("subject")] << "from" << certificateData[QStringLiteral("issuer")] << "fingerprint" << sha256Fingerprint << "serialNumber" << sslCertificate.serialNumber();
+
+        setSha256Fingerprint(sha256Fingerprint);
+        Q_EMIT isSetupChanged();
+        return;
+    }
+}
+
+}
diff --git a/src/libsync/clientsideencryptiontokenselector.h b/src/libsync/clientsideencryptiontokenselector.h
new file mode 100644 (file)
index 0000000..1a82f3d
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright Â© 2023, Matthieu Gallien <matthieu.gallien@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef CLIENTSIDETOKENSELECTOR_H
+#define CLIENTSIDETOKENSELECTOR_H
+
+#include "accountfwd.h"
+#include "owncloudlib.h"
+
+#include <QObject>
+#include <QFuture>
+
+namespace OCC
+{
+
+class OWNCLOUDSYNC_EXPORT ClientSideEncryptionTokenSelector : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(bool isSetup READ isSetup NOTIFY isSetupChanged)
+
+    Q_PROPERTY(QByteArray sha256Fingerprint READ sha256Fingerprint WRITE setSha256Fingerprint NOTIFY sha256FingerprintChanged)
+
+public:
+    explicit ClientSideEncryptionTokenSelector(QObject *parent = nullptr);
+
+    [[nodiscard]] bool isSetup() const;
+
+    [[nodiscard]] QByteArray sha256Fingerprint() const;
+
+    void clear();
+
+public slots:
+    QFuture<void> searchForCertificates(const OCC::AccountPtr &account);
+
+    void setSha256Fingerprint(const QByteArray &sha256Fingerprint);
+
+signals:
+
+    void isSetupChanged();
+
+    void sha256FingerprintChanged();
+
+    void failedToInitialize(const OCC::AccountPtr &account);
+
+private:
+    void discoverCertificates(const OCC::AccountPtr &account);
+
+    [[nodiscard]] QVariantList discoveredCertificates() const;
+
+    void processDiscoveredCertificates();
+
+    QVariantList _discoveredCertificates;
+
+    QByteArray _sha256Fingerprint;
+};
+
+}
+
+#endif // CLIENTSIDETOKENSELECTOR_H
index 853284f13f5c331f742916e8fd92baf4765d4988..6da9d6732f1295d73eea265909b2e0953b510d0c 100644 (file)
@@ -85,6 +85,16 @@ void ProcessDirectoryJob::start()
 {
     qCInfo(lcDisco) << "STARTING" << _currentFolder._server << _queryServer << _currentFolder._local << _queryLocal;
 
+    if (isInsideEncryptedTree()) {
+        auto folderDbRecord = SyncJournalFileRecord{};
+        if (_discoveryData->_statedb->getFileRecord(_currentFolder._local, &folderDbRecord) && folderDbRecord.isValid()) {
+            if (_discoveryData->_account->encryptionCertificateFingerprint() != folderDbRecord._e2eCertificateFingerprint) {
+                qCDebug(lcDisco) << "encryption certificate needs update. Forcing full discovery";
+                _queryServer = NormalQuery;
+            }
+        }
+    }
+
     _discoveryData->_noCaseConflictRecordsInDb = _discoveryData->_statedb->caseClashConflictRecordPaths().isEmpty();
 
     if (_queryServer == NormalQuery) {
@@ -231,7 +241,7 @@ void ProcessDirectoryJob::process()
             continue;
 
         const auto isEncryptedFolderButE2eIsNotSetup = e.serverEntry.isValid() && e.serverEntry.isE2eEncrypted() &&
-            _discoveryData->_account->e2e() && !_discoveryData->_account->e2e()->_publicKey.isNull() && _discoveryData->_account->e2e()->_privateKey.isNull();
+            _discoveryData->_account->e2e() && !_discoveryData->_account->e2e()->isInitialized();
 
         if (isEncryptedFolderButE2eIsNotSetup) {
             checkAndUpdateSelectiveSyncListsForE2eeFolders(path._server + "/");
@@ -697,6 +707,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(const SyncFileItemPtr &it
     item->_e2eEncryptionStatus = serverEntry.isE2eEncrypted() ? SyncFileItem::EncryptionStatus::Encrypted : SyncFileItem::EncryptionStatus::NotEncrypted;
     if (serverEntry.isE2eEncrypted()) {
         item->_e2eEncryptionServerCapability = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(_discoveryData->_account->capabilities().clientSideEncryptionVersion());
+        item->_e2eCertificateFingerprint = serverEntry.e2eCertificateFingerprint;
     }
     item->_encryptedFileName = [=] {
         if (serverEntry.e2eMangledName.isEmpty()) {
@@ -1161,7 +1172,10 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
 
     if (dbEntry.isValid()) {
         bool typeChange = localEntry.isDirectory != dbEntry.isDirectory();
-        if (!typeChange && localEntry.isVirtualFile) {
+        if (localEntry.isDirectory && dbEntry.isValid() && dbEntry.isE2eEncrypted() && dbEntry._e2eCertificateFingerprint != _discoveryData->_account->encryptionCertificateFingerprint()) {
+            item->_instruction = CSYNC_INSTRUCTION_UPDATE_ENCRYPTION_METADATA;
+            item->_direction = SyncFileItem::Up;
+        } else if (!typeChange && localEntry.isVirtualFile) {
             if (noServerEntry) {
                 item->_instruction = CSYNC_INSTRUCTION_REMOVE;
                 item->_direction = SyncFileItem::Down;
@@ -1410,6 +1424,7 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
             // base is a record in the SyncJournal database that contains the data about the being-renamed folder with it's old name and encryption information
             item->_e2eEncryptionStatus = EncryptionStatusEnums::fromDbEncryptionStatus(base._e2eEncryptionStatus);
             item->_e2eEncryptionServerCapability = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(_discoveryData->_account->capabilities().clientSideEncryptionVersion());
+            item->_e2eCertificateFingerprint = base._e2eCertificateFingerprint;
         }
         postProcessLocalNew();
         finalize();
@@ -1715,6 +1730,12 @@ void ProcessDirectoryJob::processFileFinalize(
         }
     }
 
+    if (item->_direction == SyncFileItem::Up && item->isEncrypted() && !_discoveryData->_account->e2e()->canEncrypt()) {
+        item->_instruction = CSYNC_INSTRUCTION_ERROR;
+        item->_errorString = tr("Cannot modify encrypted item because the selected certificate is not valid.");
+        item->_status = SyncFileItem::Status::NormalError;
+    }
+
     if (item->isDirectory() && item->_instruction == CSYNC_INSTRUCTION_SYNC)
         item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
     bool removed = item->_instruction == CSYNC_INSTRUCTION_REMOVE;
@@ -2112,6 +2133,7 @@ DiscoverySingleDirectoryJob *ProcessDirectoryJob::startAsyncServerQuery()
                 const auto alreadyDownloaded = _discoveryData->_statedb->getFileRecord(_dirItem->_file, &record) && record.isValid();
                 // we need to make sure we first download all e2ee files/folders before migrating
                 _dirItem->_isEncryptedMetadataNeedUpdate = alreadyDownloaded && serverJob->encryptedMetadataNeedUpdate();
+                _dirItem->_e2eCertificateFingerprint = serverJob->certificateSha256Fingerprint();
                 _dirItem->_e2eEncryptionStatus = serverJob->currentEncryptionStatus();
                 _dirItem->_e2eEncryptionStatusRemote = serverJob->currentEncryptionStatus();
                 _dirItem->_e2eEncryptionServerCapability = serverJob->requiredEncryptionStatus();
index 6cd226f2ee1a5bd5383fdd7eedd18e1f3574ae0d..0114127ff7207ec767c74094e7d3997bc7d8fe74 100644 (file)
@@ -460,6 +460,11 @@ SyncFileItem::EncryptionStatus DiscoverySingleDirectoryJob::requiredEncryptionSt
     return _encryptionStatusRequired;
 }
 
+QByteArray DiscoverySingleDirectoryJob::certificateSha256Fingerprint() const
+{
+    return _e2eCertificateFingerprint;
+}
+
 static void propertyMapToRemoteInfo(const QMap<QString, QString> &map, RemotePermissions::MountedPermissionAlgorithm algorithm, RemoteInfo &result)
 {
     for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
@@ -728,6 +733,7 @@ void DiscoverySingleDirectoryJob::metadataReceived(const QJsonDocument &json, in
         }
         _isFileDropDetected = e2EeFolderMetadata->isFileDropPresent();
         _encryptedMetadataNeedUpdate = e2EeFolderMetadata->encryptedMetadataNeedUpdate();
+        _e2eCertificateFingerprint = e2EeFolderMetadata->certificateSha256Fingerprint();
         _encryptionStatusRequired = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(_account->capabilities().clientSideEncryptionVersion());
         _encryptionStatusCurrent = e2EeFolderMetadata->existingMetadataEncryptionStatus();
 
index bb932f568b4db0f7fbc6d2cb003e50bcb2c9f340..f1d1d3136dd654135ab60623decf08e2649d72d9 100644 (file)
@@ -71,6 +71,7 @@ struct RemoteInfo
     bool _isE2eEncrypted = false;
     bool isFileDropDetected = false;
     QString e2eMangledName;
+    QByteArray e2eCertificateFingerprint;
     bool sharedByMe = false;
 
     [[nodiscard]] bool isValid() const { return !name.isNull(); }
@@ -162,6 +163,7 @@ public:
     void abort();
     [[nodiscard]] bool isFileDropDetected() const;
     [[nodiscard]] bool encryptedMetadataNeedUpdate() const;
+    [[nodiscard]] QByteArray certificateSha256Fingerprint() const;
     [[nodiscard]] SyncFileItem::EncryptionStatus currentEncryptionStatus() const;
     [[nodiscard]] SyncFileItem::EncryptionStatus requiredEncryptionStatus() const;
 
@@ -202,6 +204,8 @@ private:
     bool _isFileDropDetected = false;
     bool _encryptedMetadataNeedUpdate = false;
     SyncFileItem::EncryptionStatus _encryptionStatusRequired = SyncFileItem::EncryptionStatus::NotEncrypted;
+    QByteArray _e2eCertificateFingerprint;
+
     // If set, the discovery will finish with an error
     int64_t _size = 0;
     QString _error;
index 65a394646cc8ce696b6fe464afa0e94d7c67ebb9..7dfcb11e80c1b09cb9558c27dccc043e537a84e0 100644 (file)
@@ -97,7 +97,7 @@ void EncryptedFolderMetadataHandler::lockFolder()
         return;
     }
 
-    const auto lockJob = new LockEncryptFolderApiJob(_account, _folderId, _journalDb, _account->e2e()->_publicKey, this);
+    const auto lockJob = new LockEncryptFolderApiJob(_account, _folderId, _account->e2e()->certificateSha256Fingerprint(), _journalDb, _account->e2e()->getPublicKey(), this);
     connect(lockJob, &LockEncryptFolderApiJob::success, this, &EncryptedFolderMetadataHandler::slotFolderLockedSuccessfully);
     connect(lockJob, &LockEncryptFolderApiJob::error, this, &EncryptedFolderMetadataHandler::slotFolderLockedError);
     if (_account->capabilities().clientSideEncryptionVersion() >= 2.0) {
@@ -265,13 +265,13 @@ void EncryptedFolderMetadataHandler::unlockFolder(const UnlockFolderWithResult r
     qCDebug(lcFetchAndUploadE2eeFolderMetadataJob) << "Calling Unlock";
 
     const auto unlockJob = new UnlockEncryptFolderApiJob(_account, _folderId, _folderToken, _journalDb, this);
-    connect(unlockJob, &UnlockEncryptFolderApiJob::success, [this](const QByteArray &folderId) {
+    connect(unlockJob, &UnlockEncryptFolderApiJob::success, unlockJob, [this](const QByteArray &folderId) {
         qDebug(lcFetchAndUploadE2eeFolderMetadataJob) << "Successfully Unlocked";
         _isFolderLocked = false;
         emit folderUnlocked(folderId, 200);
         _isUnlockRunning = false;
     });
-    connect(unlockJob, &UnlockEncryptFolderApiJob::error, [this](const QByteArray &folderId, int httpStatus) {
+    connect(unlockJob, &UnlockEncryptFolderApiJob::error, unlockJob, [this](const QByteArray &folderId, int httpStatus) {
         qDebug(lcFetchAndUploadE2eeFolderMetadataJob) << "Unlock Error";
         emit folderUnlocked(folderId, httpStatus);
         _isUnlockRunning = false;
index 295e24dd0c60f8c985c1826176801698c91805d0..e7740a85b63872afa69dec6ed061f1b880cba875 100644 (file)
@@ -83,6 +83,7 @@ void EncryptFolderJob::slotEncryptionFlagSuccess(const QByteArray &fileId)
 
     if (!rec.isE2eEncrypted()) {
         rec._e2eEncryptionStatus = SyncJournalFileRecord::EncryptionStatus::Encrypted;
+        rec._e2eCertificateFingerprint = _account->e2e()->certificateSha256Fingerprint();
         const auto result = _journal->setFileRecord(rec);
         if (!result) {
             qCWarning(lcEncryptFolderJob) << "Error when setting the file record to the database" << rec._path << result.error();
index 38ace714d0f693d6a22159ea7bc739ed0702f820..fd4ed36ba5a87bc4c1672bc606a1e324835e9d35 100644 (file)
@@ -24,7 +24,7 @@
 
 namespace OCC
 {
-Q_LOGGING_CATEGORY(lcCseMetadata, "nextcloud.metadata", QtInfoMsg)
+Q_LOGGING_CATEGORY(lcCseMetadata, "nextcloud.sync.clientsideencryption.metadata", QtInfoMsg)
 
 namespace
 {
@@ -187,23 +187,24 @@ void FolderMetadata::setupExistingMetadata(const QByteArray &metadata)
         return;
     }
 
-    if (!parseFileDropPart(metaDataDoc)) {
-        qCDebug(lcCseMetadata()) << "Could not parse filedrop part";
-        return;
-    }
-
     if (_folderUsers.contains(_account->davUser())) {
         const auto currentFolderUser = _folderUsers.value(_account->davUser());
-        _metadataKeyForEncryption = decryptDataWithPrivateKey(currentFolderUser.encryptedMetadataKey);
+        _e2eCertificateFingerprint = QSslCertificate{currentFolderUser.certificatePem}.digest(QCryptographicHash::Sha256).toBase64();
+        _metadataKeyForEncryption = QByteArray::fromBase64(decryptDataWithPrivateKey(currentFolderUser.encryptedMetadataKey, _e2eCertificateFingerprint));
         _metadataKeyForDecryption = _metadataKeyForEncryption;
     }
 
+    if (!parseFileDropPart(metaDataDoc)) {
+        qCDebug(lcCseMetadata()) << "Could not parse filedrop part";
+        return;
+    }
+
     if (metadataKeyForDecryption().isEmpty() || metadataKeyForEncryption().isEmpty()) {
         qCDebug(lcCseMetadata()) << "Could not setup metadata key!";
         _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
         return;
     }
-    
+
     const auto metadataObj = metaDataDoc.object()[metadataJsonKey].toObject();
     _metadataNonce = QByteArray::fromBase64(metadataObj[nonceKey].toString().toLocal8Bit());
     const auto cipherTextEncrypted = metadataObj[cipherTextKey].toString().toLocal8Bit();
@@ -284,7 +285,7 @@ void FolderMetadata::setupExistingMetadataLegacy(const QByteArray &metadata)
     const auto metadataKeyFromJson = metadataObj[metadataKeyKey].toString().toLocal8Bit();
     if (!metadataKeyFromJson.isEmpty()) {
         // parse version 1.1 and 1.2 (both must have a single "metadataKey"), not "metadataKeys" as 1.0
-        const auto decryptedMetadataKeyBase64 = decryptDataWithPrivateKey(QByteArray::fromBase64(metadataKeyFromJson));
+        const auto decryptedMetadataKeyBase64 = decryptDataWithPrivateKey(QByteArray::fromBase64(metadataKeyFromJson), _account->e2e()->certificateSha256Fingerprint());
         if (!decryptedMetadataKeyBase64.isEmpty()) {
             // fromBase64() multiple times just to stick with the old wrong way
             _metadataKeyForDecryption = QByteArray::fromBase64(QByteArray::fromBase64(decryptedMetadataKeyBase64));
@@ -306,7 +307,7 @@ void FolderMetadata::setupExistingMetadataLegacy(const QByteArray &metadata)
         if (!lastMetadataKeyFromJson.isEmpty()) {
             const auto lastMetadataKeyValueFromJson = metadataKeys.value(lastMetadataKeyFromJson).toString().toLocal8Bit();
             if (!lastMetadataKeyValueFromJson.isEmpty()) {
-                const auto lastMetadataKeyValueFromJsonBase64 = decryptDataWithPrivateKey(QByteArray::fromBase64(lastMetadataKeyValueFromJson));
+                const auto lastMetadataKeyValueFromJsonBase64 = decryptDataWithPrivateKey(QByteArray::fromBase64(lastMetadataKeyValueFromJson), _account->e2e()->certificateSha256Fingerprint());
                 if (!lastMetadataKeyValueFromJsonBase64.isEmpty()) {
                     _metadataKeyForDecryption = QByteArray::fromBase64(QByteArray::fromBase64(lastMetadataKeyValueFromJsonBase64));
                 }
@@ -429,29 +430,31 @@ void FolderMetadata::emitSetupComplete()
 }
 
 // RSA/ECB/OAEPWithSHA-256AndMGF1Padding using private / public key.
-QByteArray FolderMetadata::encryptDataWithPublicKey(const QByteArray &data, const QSslKey &key) const
+QByteArray FolderMetadata::encryptDataWithPublicKey(const QByteArray &binaryData,
+                                                    const CertificateInformation &shareUserCertificate) const
 {
-    Bio publicKeyBio;
-    const auto publicKeyPem = key.toPem();
-    BIO_write(publicKeyBio, publicKeyPem.constData(), publicKeyPem.size());
-    const auto publicKey = PKey::readPublicKey(publicKeyBio);
+    const auto encryptBase64Result = EncryptionHelper::encryptStringAsymmetric(shareUserCertificate, _account->e2e()->paddingMode(), *_account->e2e(), binaryData);
 
-    // The metadata key is binary so base64 encode it first
-    return EncryptionHelper::encryptStringAsymmetric(publicKey, data);
+    if (encryptBase64Result) {
+        return *encryptBase64Result;
+    } else {
+        qCWarning(lcCseMetadata()) << "fail to encryptDataWithPublicKey";
+        _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
+        return {};
+    }
+    return {};
 }
 
-QByteArray FolderMetadata::decryptDataWithPrivateKey(const QByteArray &data) const
+QByteArray FolderMetadata::decryptDataWithPrivateKey(const QByteArray &base64Data,
+                                                     const QByteArray &certificateFingerprint) const
 {
-    Bio privateKeyBio;
-    BIO_write(privateKeyBio, _account->e2e()->_privateKey.constData(), _account->e2e()->_privateKey.size());
-    const auto privateKey = PKey::readPrivateKey(privateKeyBio);
-
-    const auto decryptResult = EncryptionHelper::decryptStringAsymmetric(privateKey, data);
-    if (decryptResult.isEmpty()) {
+    const auto decryptBase64Result = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getCertificateInformationByFingerprint(certificateFingerprint), _account->e2e()->paddingMode(), *_account->e2e(), base64Data);
+    if (!decryptBase64Result) {
         qCDebug(lcCseMetadata()) << "ERROR. Could not decrypt the metadata key";
         _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
+        return {};
     }
-    return decryptResult;
+    return *decryptBase64Result;
 }
 
 // AES/GCM/NoPadding (128 bit key size)
@@ -476,7 +479,8 @@ QByteArray FolderMetadata::computeMetadataKeyChecksum(const QByteArray &metadata
 {
     auto hashAlgorithm = QCryptographicHash{QCryptographicHash::Sha256};
 
-    hashAlgorithm.addData(_account->e2e()->_mnemonic.remove(' ').toUtf8());
+    auto mnemonic = _account->e2e()->getMnemonic();
+    hashAlgorithm.addData(mnemonic.remove(' ').toUtf8());
     auto sortedFiles = _files;
     std::sort(sortedFiles.begin(), sortedFiles.end(), [](const auto &first, const auto &second) {
         return first.encryptedFilename < second.encryptedFilename;
@@ -554,7 +558,7 @@ void FolderMetadata::initEmptyMetadata()
     }
     qCDebug(lcCseMetadata()) << "Setting up empty metadata v2";
     if (_isRootEncryptedFolder) {
-        if (!addUser(_account->davUser(), _account->e2e()->_certificate)) {
+        if (!addUser(_account->davUser(), _account->e2e()->getCertificate())) {
             qCDebug(lcCseMetadata) << "Empty metadata setup failed. Could not add first user.";
             _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
             return;
@@ -571,7 +575,7 @@ void FolderMetadata::initEmptyMetadataLegacy()
     qCDebug(lcCseMetadata) << "Settint up legacy empty metadata";
     _metadataKeyForEncryption = EncryptionHelper::generateRandom(metadataKeySize);
     _metadataKeyForDecryption = _metadataKeyForEncryption;
-    QString publicKey = _account->e2e()->_publicKey.toPem().toBase64();
+    QString publicKey = _account->e2e()->getPublicKey().toPem().toBase64();
     QString displayName = _account->displayName();
 
     _isMetadataValid = true;
@@ -703,7 +707,7 @@ QByteArray FolderMetadata::encryptedMetadataLegacy()
     }
     const auto version = _account->capabilities().clientSideEncryptionVersion();
     // multiple toBase64() just to keep with the old (wrong way)
-    const auto encryptedMetadataKey = encryptDataWithPublicKey(metadataKeyForEncryption().toBase64().toBase64(), _account->e2e()->_publicKey).toBase64();
+    const auto encryptedMetadataKey = encryptDataWithPublicKey(metadataKeyForEncryption().toBase64().toBase64(), _account->e2e()->getCertificateInformation()).toBase64();
     const QJsonObject metadata{
         {versionKey, version},
         {metadataKeyKey, QJsonValue::fromVariant(encryptedMetadataKey)},
@@ -796,7 +800,7 @@ bool FolderMetadata::parseFileDropPart(const QJsonDocument &doc)
             if (userParsedId == _account->davUser()) {
                 const auto fileDropEntryUser = UserWithFileDropEntryAccess{
                     userParsedId,
-                    decryptDataWithPrivateKey(QByteArray::fromBase64(userParsed.value(usersEncryptedFiledropKey).toByteArray()))};
+                    decryptDataWithPrivateKey(QByteArray::fromBase64(userParsed.value(usersEncryptedFiledropKey).toByteArray()), _e2eCertificateFingerprint)};
                 if (!fileDropEntryUser.isValid()) {
                     qCDebug(lcCseMetadata()) << "Could not parse filedrop data. encryptedFiledropKey decryption failed";
                     _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
@@ -957,6 +961,11 @@ bool FolderMetadata::encryptedMetadataNeedUpdate() const
     return !foundNestedFoldersOrIsNestedFolder;
 }
 
+QByteArray FolderMetadata::certificateSha256Fingerprint() const
+{
+    return _e2eCertificateFingerprint;
+}
+
 bool FolderMetadata::moveFromFileDropToFiles()
 {
     if (_fileDropEntries.isEmpty()) {
@@ -1037,13 +1046,14 @@ void FolderMetadata::slotRootE2eeFolderMetadataReceived(int statusCode, const QS
 bool FolderMetadata::addUser(const QString &userId, const QSslCertificate &certificate)
 {
     Q_ASSERT(_isRootEncryptedFolder);
+    Q_ASSERT(!certificate.isNull());
     if (!_isRootEncryptedFolder) {
         qCWarning(lcCseMetadata()) << "Could not add a folder user to a non top level folder.";
         return false;
     }
 
-    const auto certificatePublicKey = certificate.publicKey();
-    if (userId.isEmpty() || certificate.isNull() || certificatePublicKey.isNull()) {
+    const auto shareUserCertificate = CertificateInformation{{}, QSslCertificate{certificate}};
+    if (userId.isEmpty() || certificate.isNull() || !shareUserCertificate.canEncrypt()) {
         qCWarning(lcCseMetadata()) << "Could not add a folder user. Invalid userId or certificate.";
         return false;
     }
@@ -1052,7 +1062,7 @@ bool FolderMetadata::addUser(const QString &userId, const QSslCertificate &certi
     UserWithFolderAccess newFolderUser;
     newFolderUser.userId = userId;
     newFolderUser.certificatePem = certificate.toPem();
-    newFolderUser.encryptedMetadataKey = encryptDataWithPublicKey(metadataKeyForEncryption(), certificatePublicKey);
+    newFolderUser.encryptedMetadataKey = encryptDataWithPublicKey(metadataKeyForEncryption(), shareUserCertificate);
     _folderUsers[userId] = newFolderUser;
     updateUsersEncryptedMetadataKey();
 
@@ -1095,13 +1105,9 @@ void FolderMetadata::updateUsersEncryptedMetadataKey()
         auto folderUser = it.value();
 
         const QSslCertificate certificate(folderUser.certificatePem);
-        const auto certificatePublicKey = certificate.publicKey();
-        if (certificate.isNull() || certificatePublicKey.isNull()) {
-            qCWarning(lcCseMetadata()) << "Could not update folder users with null certificatePublicKey!";
-            continue;
-        }
+        CertificateInformation shareUserCertificate = CertificateInformation{{}, QSslCertificate{certificate}};
 
-        const auto encryptedMetadataKey = encryptDataWithPublicKey(metadataKeyForEncryption(), certificatePublicKey);
+        const auto encryptedMetadataKey = encryptDataWithPublicKey(metadataKeyForEncryption(), shareUserCertificate);
         if (encryptedMetadataKey.isEmpty()) {
             qCWarning(lcCseMetadata()) << "Could not update folder users with empty encryptedMetadataKey!";
             continue;
index 0cdba5236effdbe7cd401001245acbbdcae05659..34fda88e86e544d4a658fbc7f473c300c2fc9954 100644 (file)
@@ -116,6 +116,8 @@ public:
 
     [[nodiscard]] bool encryptedMetadataNeedUpdate() const;
 
+    [[nodiscard]] QByteArray certificateSha256Fingerprint() const;
+
     [[nodiscard]] bool moveFromFileDropToFiles();
 
     // adds a user to have access to this folder (always generates new metadata key)
@@ -141,8 +143,8 @@ public:
     [[nodiscard]] QByteArray initialMetadata() const;
 
 public slots:
-    void addEncryptedFile(const EncryptedFile &f);
-    void removeEncryptedFile(const EncryptedFile &f);
+    void addEncryptedFile(const FolderMetadata::EncryptedFile &f);
+    void removeEncryptedFile(const FolderMetadata::EncryptedFile &f);
     void removeAllEncryptedFiles();
 
 private:
@@ -150,8 +152,9 @@ private:
 
     [[nodiscard]] bool verifyMetadataKey(const QByteArray &metadataKey) const;
 
-    [[nodiscard]] QByteArray encryptDataWithPublicKey(const QByteArray &data, const QSslKey &key) const;
-    [[nodiscard]] QByteArray decryptDataWithPrivateKey(const QByteArray &data) const;
+    [[nodiscard]] QByteArray encryptDataWithPublicKey(const QByteArray &data,
+                                                      const CertificateInformation &shareUserCertificate) const;
+    [[nodiscard]] QByteArray decryptDataWithPrivateKey(const QByteArray &data, const QByteArray &certificateFingerprint) const;
 
     [[nodiscard]] QByteArray encryptJsonObject(const QByteArray& obj, const QByteArray pass) const;
     [[nodiscard]] QByteArray decryptJsonObject(const QByteArray& encryptedJsonBlob, const QByteArray& pass) const;
@@ -232,6 +235,8 @@ private:
     // signature from server-side metadata
     QByteArray _initialSignature;
 
+    QByteArray _e2eCertificateFingerprint;
+
     // both files and folders info
     QVector<EncryptedFile> _files;
 
index cce9522129b45301f8591754419a2b0243cfb371..2cfbc9566e96f2e75ba3445bf966df20978f5887 100644 (file)
@@ -394,10 +394,21 @@ PropagateItemJob *OwncloudPropagator::createJob(const SyncFileItemPtr &item)
         }
     case CSYNC_INSTRUCTION_UPDATE_VFS_METADATA:
         return new PropagateVfsUpdateMetadataJob(this, item);
+    case CSYNC_INSTRUCTION_UPDATE_ENCRYPTION_METADATA:
+    {
+        const auto rootE2eeFolderPath = item->_file.split('/').first();
+        const auto rootE2eeFolderPathFullRemotePath = fullRemotePath(rootE2eeFolderPath);
+        return new UpdateMigratedE2eeMetadataJob(this, item, rootE2eeFolderPathFullRemotePath, remotePath());
+    }
     case CSYNC_INSTRUCTION_IGNORE:
     case CSYNC_INSTRUCTION_ERROR:
         return new PropagateIgnoreJob(this, item);
-    default:
+    case CSYNC_INSTRUCTION_NONE:
+    case CSYNC_INSTRUCTION_EVAL:
+    case CSYNC_INSTRUCTION_EVAL_RENAME:
+    case CSYNC_INSTRUCTION_STAT_ERROR:
+    case CSYNC_INSTRUCTION_UPDATE_METADATA:
+    case CSYNC_INSTRUCTION_CASE_CLASH_CONFLICT:
         return nullptr;
     }
     return nullptr;
@@ -1521,6 +1532,9 @@ void PropagateDirectory::slotSubJobsFinished(SyncFileItem::Status status)
             }
 #endif
             if (!_item->_isAnyCaseClashChild && !_item->_isAnyInvalidCharChild) {
+                if (_item->isEncrypted()) {
+                    _item->_e2eCertificateFingerprint = propagator()->account()->encryptionCertificateFingerprint();
+                }
                 const auto result = propagator()->updateMetadata(*_item);
                 if (!result) {
                     status = _item->_status = SyncFileItem::FatalError;
index b656d6adadbe0ac187620203760e03089b42a5d6..3272be55103ecbc83c999f7f544daff2e4bd7b70 100644 (file)
@@ -58,6 +58,8 @@ QString Progress::asResultString(const SyncFileItem &item)
         return QCoreApplication::translate("progress", "Updated local metadata");
     case CSYNC_INSTRUCTION_UPDATE_VFS_METADATA:
         return QCoreApplication::translate("progress", "Updated local virtual files metadata");
+    case CSYNC_INSTRUCTION_UPDATE_ENCRYPTION_METADATA:
+        return QCoreApplication::translate("progress", "Updated end-to-end encryption metadata");
     case CSYNC_INSTRUCTION_NONE:
     case CSYNC_INSTRUCTION_EVAL:
         return QCoreApplication::translate("progress", "Unknown");
@@ -91,6 +93,8 @@ QString Progress::asActionString(const SyncFileItem &item)
         return QCoreApplication::translate("progress", "Updating local metadata");
     case CSYNC_INSTRUCTION_UPDATE_VFS_METADATA:
         return QCoreApplication::translate("progress", "Updating local virtual files metadata");
+    case CSYNC_INSTRUCTION_UPDATE_ENCRYPTION_METADATA:
+        return QCoreApplication::translate("progress", "Updating end-to-end encryption metadata");
     case CSYNC_INSTRUCTION_NONE:
     case CSYNC_INSTRUCTION_EVAL:
         break;
index 4f644315067808b23c92715bf38af0f0ae4bbdf4..36f1251dbedac3372662dcfca0b1e0aa67cddc60 100644 (file)
@@ -1172,6 +1172,7 @@ void PropagateDownloadFile::finalizeDownload()
 {
     if (isEncrypted()) {
         if (_downloadEncryptedHelper->decryptFile(_tmpFile)) {
+            _item->_e2eCertificateFingerprint = propagator()->account()->encryptionCertificateFingerprint();
             downloadFinished();
         } else {
             done(SyncFileItem::NormalError, _downloadEncryptedHelper->errorString(), ErrorCategory::GenericError);
index 568aa911a91fe2aaae8a78caf64254ccbc7514b5..52abaec5be0eee3bee06eb49d5bd1a025837a93e 100644 (file)
@@ -18,7 +18,6 @@ PropagateDownloadEncrypted::PropagateDownloadEncrypted(OwncloudPropagator *propa
     const auto rootPath = Utility::noLeadingSlashPath(_propagator->remotePath());
     const auto remoteFilename = _item->_encryptedFileName.isEmpty() ? _item->_file : _item->_encryptedFileName;
     const auto remotePath = QString(rootPath + remoteFilename);
-    const auto remoteParentPath = remotePath.left(remotePath.lastIndexOf('/'));
     _remoteParentPath = remotePath.left(remotePath.lastIndexOf('/'));
 
     const auto filenameInDb = _item->_file;
@@ -115,4 +114,4 @@ QString PropagateDownloadEncrypted::errorString() const
   return _errorString;
 }
 
-}
\ No newline at end of file
+}
index e301beed5a7234dfdc9d76b4ff20de05d9788ec9..868614e68f8f4bcc5790e025fbb3be48b3030a86 100644 (file)
@@ -261,6 +261,7 @@ void PropagateRemoteMkdir::slotEncryptFolderFinished(int status, EncryptionStatu
     qCDebug(lcPropagateRemoteMkdir) << "Success making the new folder encrypted";
     propagator()->_activeJobList.removeOne(this);
     _item->_e2eEncryptionStatus = encryptionStatus;
+    _item->_e2eCertificateFingerprint = propagator()->account()->encryptionCertificateFingerprint();
     _item->_e2eEncryptionStatusRemote = encryptionStatus;
     if (_item->isEncrypted()) {
         _item->_e2eEncryptionServerCapability = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(propagator()->account()->capabilities().clientSideEncryptionVersion());
index 16e68c96b5ac60a6ddb611597cab81c675f75d56..3fea412308222785819ccaa03038841c78a2fb09 100644 (file)
@@ -806,6 +806,10 @@ void PropagateUploadFileCommon::finalize()
     if (quotaIt != propagator()->_folderQuota.end())
         quotaIt.value() -= _fileToUpload._size;
 
+    if (_item->isEncrypted() && _uploadingEncrypted) {
+        _item->_e2eCertificateFingerprint = propagator()->account()->encryptionCertificateFingerprint();
+    }
+
     // Update the database entry
     const auto result = propagator()->updateMetadata(*_item, Vfs::DatabaseMetadata);
     if (!result) {
index 6999459fbc686b1c156540ab41178ad4c4cf8970..0f156b0282d5f562949c382f1a465e56699d293f 100644 (file)
@@ -186,4 +186,4 @@ void PropagateUploadEncrypted::slotUploadMetadataFinished(int statusCode, const
                    FileSystem::getSize(_completeFileName));
 }
 
-} // namespace OCC
\ No newline at end of file
+} // namespace OCC
index 21b20beb42a2edabbe673dea63504eb1db1e817f..2ac90bf1e8df3b0d0124bcb689bb10cfe7389188 100644 (file)
@@ -423,6 +423,10 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item)
                 }
             }
 
+            if (rec.isE2eEncrypted()) {
+                rec._e2eCertificateFingerprint = _account->encryptionCertificateFingerprint();
+            }
+
             // Updating the db happens on success
             if (!_journal->setFileRecord(rec)) {
                 item->_status = SyncFileItem::Status::NormalError;
@@ -513,9 +517,13 @@ void SyncEngine::startSync()
             for (const auto &e2EeLockedFolder : e2EeLockedFolders) {
                 const auto folderId = e2EeLockedFolder.first;
                 qCInfo(lcEngine()) << "start unlock job for folderId:" << folderId;
-                const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->_privateKey, e2EeLockedFolder.second);
+                const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getCertificateInformation(), _account->e2e()->paddingMode(), *_account->e2e(), e2EeLockedFolder.second);
+                if (!folderToken) {
+                    qCWarning(lcEngine()) << "decrypt failed";
+                    return;
+                }
                 // TODO: We need to rollback changes done to metadata in case we have an active lock, this needs to be implemented on the server first
-                const auto unlockJob = new OCC::UnlockEncryptFolderApiJob(_account, folderId, folderToken, _journal, this);
+                const auto unlockJob = new OCC::UnlockEncryptFolderApiJob(_account, folderId, *folderToken, _journal, this);
                 unlockJob->setShouldRollbackMetadataChanges(true);
                 unlockJob->start();
             }
index 2746b192cd4bfd4a44f8acd118c35100fb6985da..86243c34052c8a217036141812d2d67fa21e1447 100644 (file)
@@ -118,6 +118,7 @@ SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QStri
     rec._checksumHeader = _checksumHeader;
     rec._e2eMangledName = _encryptedFileName.toUtf8();
     rec._e2eEncryptionStatus = EncryptionStatusEnums::toDbEncryptionStatus(_e2eEncryptionStatus);
+    rec._e2eCertificateFingerprint = _e2eCertificateFingerprint;
     rec._lockstate._locked = _locked == LockStatus::LockedItem;
     rec._lockstate._lockOwnerDisplayName = _lockOwnerDisplayName;
     rec._lockstate._lockOwnerId = _lockOwnerId;
@@ -158,6 +159,7 @@ SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRec
     item->_encryptedFileName = rec.e2eMangledName();
     item->_e2eEncryptionStatus = EncryptionStatusEnums::fromDbEncryptionStatus(rec._e2eEncryptionStatus);
     item->_e2eEncryptionServerCapability = item->_e2eEncryptionStatus;
+    item->_e2eCertificateFingerprint = rec._e2eCertificateFingerprint;
     item->_locked = rec._lockstate._locked ? LockStatus::LockedItem : LockStatus::UnlockedItem;
     item->_lockOwnerDisplayName = rec._lockstate._lockOwnerDisplayName;
     item->_lockOwnerId = rec._lockstate._lockOwnerId;
index 46ee49621c68856621248cd8629f15a48d105d98..71dc973875dac548c923cab699da87d72e0397de 100644 (file)
@@ -286,6 +286,7 @@ public:
     EncryptionStatus _e2eEncryptionStatus = EncryptionStatus::NotEncrypted; // The file is E2EE or the content of the directory should be E2EE
     EncryptionStatus _e2eEncryptionServerCapability = EncryptionStatus::NotEncrypted;
     EncryptionStatus _e2eEncryptionStatusRemote = EncryptionStatus::NotEncrypted;
+    QByteArray _e2eCertificateFingerprint;
     quint16 _httpErrorCode = 0;
     RemotePermissions _remotePerm;
     QString _errorString; // Contains a string only in case of error
index 5ea537c81626116633335f84af81d1d06a96d276..ecfa7c5d104fe056974e7129bc990382aee71b64 100644 (file)
@@ -80,7 +80,7 @@ void UpdateE2eeFolderMetadataJob::slotFetchMetadataJobFinished(int httpReturnCod
         qCDebug(lcUpdateFileDropMetadataJob()) << "Error Getting the encrypted metadata.";
         _item->_status = SyncFileItem::FatalError;
         _item->_errorString = message;
-        finished(SyncFileItem::FatalError);
+        emit finished(SyncFileItem::FatalError);
         return;
     }
 
@@ -115,7 +115,7 @@ void UpdateE2eeFolderMetadataJob::slotUpdateMetadataFinished(int httpReturnCode,
     propagator()->_journal->schedulePathForRemoteDiscovery(_item->_file);
     propagator()->_anotherSyncNeeded = true;
     _item->_status = itemStatus;
-    finished(itemStatus);
+    emit finished(itemStatus);
 }
 
 void UpdateE2eeFolderMetadataJob::unlockFolder(const EncryptedFolderMetadataHandler::UnlockFolderWithResult result)
@@ -143,17 +143,17 @@ void UpdateE2eeFolderMetadataJob::unlockFolder(const EncryptedFolderMetadataHand
                 _item->_e2eEncryptionServerCapability = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(propagator()->account()->capabilities().clientSideEncryptionVersion());
             }
         }
-        finished(itemStatus);
+        emit finished(itemStatus);
         return;
     }
 
     qCDebug(lcUpdateFileDropMetadataJob) << "Calling Unlock";
-    connect(_encryptedFolderMetadataHandler.data(), &EncryptedFolderMetadataHandler::folderUnlocked, [this](const QByteArray &folderId, int httpStatus) {
+    connect(_encryptedFolderMetadataHandler.data(), &EncryptedFolderMetadataHandler::folderUnlocked, _encryptedFolderMetadataHandler.data(), [this](const QByteArray &folderId, int httpStatus) {
         if (httpStatus != 200) {
             qCWarning(lcUpdateFileDropMetadataJob) << "Unlock Error" << folderId << httpStatus;
             propagator()->account()->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
             _item->_errorString = tr("Failed to unlock encrypted folder.");
-            finished(SyncFileItem::FatalError);
+            emit finished(SyncFileItem::FatalError);
             return;
         }
 
@@ -163,14 +163,14 @@ void UpdateE2eeFolderMetadataJob::unlockFolder(const EncryptedFolderMetadataHand
             || !_encryptedFolderMetadataHandler->folderMetadata()->isValid()) {
             qCWarning(lcUpdateFileDropMetadataJob) << "Failed to finalize item. Invalid metadata.";
             _item->_errorString = tr("Failed to finalize item.");
-            finished(SyncFileItem::FatalError);
+            emit finished(SyncFileItem::FatalError);
             return;
         }
 
         _item->_e2eEncryptionStatus = _encryptedFolderMetadataHandler->folderMetadata()->encryptedMetadataEncryptionStatus();
         _item->_e2eEncryptionStatusRemote = _encryptedFolderMetadataHandler->folderMetadata()->encryptedMetadataEncryptionStatus();
 
-        finished(SyncFileItem::Success);
+        emit finished(SyncFileItem::Success);
     });
     _encryptedFolderMetadataHandler->unlockFolder(result);
 }
index 1dbf5bdddf45e2895df473a59e6bf49c3c5807d3..98d6b41b8ca46c70e2d023fff1af7137ac24c37c 100644 (file)
@@ -32,8 +32,7 @@ UpdateMigratedE2eeMetadataJob::UpdateMigratedE2eeMetadataJob(OwncloudPropagator
                                                              const SyncFileItemPtr &syncFileItem,
                                                              const QString &fullRemotePath,
                                                              const QString &folderRemotePath)
-    : PropagatorJob(propagator)
-    , _item(syncFileItem)
+    : PropagateItemJob(propagator, syncFileItem)
     , _fullRemotePath(fullRemotePath)
     , _folderRemotePath(Utility::noLeadingSlashPath(Utility::noTrailingSlashPath(folderRemotePath)))
 {
@@ -48,7 +47,7 @@ void UpdateMigratedE2eeMetadataJob::start()
                                                                                      UpdateE2eeFolderUsersMetadataJob::Add,
                                                                                      _fullRemotePath,
                                                                                      propagator()->account()->davUser(),
-                                                                                     propagator()->account()->e2e()->_certificate);
+                                                                                     propagator()->account()->e2e()->getCertificate());
     updateMedatadaAndSubfoldersJob->setParent(this);
     updateMedatadaAndSubfoldersJob->setSubJobSyncItems(_subJobItems);
     _subJobItems.clear();
@@ -57,6 +56,8 @@ void UpdateMigratedE2eeMetadataJob::start()
         if (code == 200) {
             _item->_e2eEncryptionStatus = updateMedatadaAndSubfoldersJob->encryptionStatus();
             _item->_e2eEncryptionStatusRemote = updateMedatadaAndSubfoldersJob->encryptionStatus();
+            _item->_e2eCertificateFingerprint = propagator()->account()->encryptionCertificateFingerprint();
+            propagator()->updateMetadata(*_item, Vfs::UpdateMetadataType::DatabaseMetadata);
             emit finished(SyncFileItem::Status::Success);
         } else {
             _item->_errorString = message;
index bf15973f2790ce30686892abb07da699fb2dbd71..1587d4fa7ba2bceb9582f084c9c8f3a3d68c0297 100644 (file)
@@ -22,12 +22,12 @@ namespace OCC {
 
 class FolderMetadata;
 
-class OWNCLOUDSYNC_EXPORT UpdateMigratedE2eeMetadataJob : public PropagatorJob
+class OWNCLOUDSYNC_EXPORT UpdateMigratedE2eeMetadataJob : public PropagateItemJob
 {
     Q_OBJECT
 
 public:
-    explicit UpdateMigratedE2eeMetadataJob(OwncloudPropagator *propagator, const SyncFileItemPtr &syncFileItem, const QString &path, const QString &folderRemotePath);
+    explicit UpdateMigratedE2eeMetadataJob(OwncloudPropagator *propagator, const SyncFileItemPtr &syncFileItem, const QString &fullRemotePath, const QString &folderRemotePath);
 
     [[nodiscard]] bool scheduleSelfOrChild() override;
 
@@ -38,10 +38,9 @@ public:
     void addSubJobItem(const QString &key, const SyncFileItemPtr &syncFileItem);
 
 private slots:
-    void start();
+    void start() override;
 
 private:
-    SyncFileItemPtr _item;
     QHash<QString, SyncFileItemPtr> _subJobItems;
     QString _fullRemotePath;
     QString _folderRemotePath;
index 9b2304d0251b623152292d0e949472d88d7a6e98..96ef34ddb35590b4c16c505e09e558053063bcf5 100644 (file)
@@ -89,13 +89,11 @@ private slots:
         QVERIFY(!publicKey.isNull());
         QVERIFY(!privateKey.isEmpty());
 
-        _account->e2e()->_certificate = cert;
-        _account->e2e()->_publicKey = publicKey;
-        _account->e2e()->_privateKey = privateKey;
+        _account->e2e()->setCertificate(cert);
+        _account->e2e()->setPrivateKey(privateKey);
 
-        _secondAccount->e2e()->_certificate = cert;
-        _secondAccount->e2e()->_publicKey = publicKey;
-        _secondAccount->e2e()->_privateKey = privateKey;
+        _secondAccount->e2e()->setCertificate(cert);
+        _secondAccount->e2e()->setPrivateKey(privateKey);
         
     }
 
@@ -137,10 +135,11 @@ private slots:
             }
 
             const auto certificatePem = folderUserObject.value("certificate").toString().toUtf8();
+            const auto certificate = QSslCertificate{certificatePem};
             const auto encryptedMetadataKey = QByteArray::fromBase64(folderUserObject.value("encryptedMetadataKey").toString().toUtf8());
 
             if (!encryptedMetadataKey.isEmpty()) {
-                const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey);
+                const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey, certificate.digest(QCryptographicHash::Sha256));
                 if (decryptedMetadataKey.isEmpty()) {
                     break;
                 }
@@ -246,11 +245,11 @@ private slots:
         encryptedFile.initializationVector = EncryptionHelper::generateRandom(16);
         metadata->addEncryptedFile(encryptedFile);
 
-        QVERIFY(metadata->addUser(_secondAccount->davUser(), _secondAccount->e2e()->_certificate));
+        QVERIFY(metadata->addUser(_secondAccount->davUser(), _secondAccount->e2e()->getCertificate()));
 
         QVERIFY(metadata->removeUser(_secondAccount->davUser()));
 
-        QVERIFY(metadata->addUser(_secondAccount->davUser(), _secondAccount->e2e()->_certificate));
+        QVERIFY(metadata->addUser(_secondAccount->davUser(), _secondAccount->e2e()->getCertificate()));
 
         const auto encryptedMetadata = metadata->encryptedMetadata();
         QVERIFY(!encryptedMetadata.isEmpty());
@@ -273,10 +272,11 @@ private slots:
             }
 
             const auto certificatePem = folderUserObject.value("certificate").toString().toUtf8();
+            const auto certificate = QSslCertificate{certificatePem};
             const auto encryptedMetadataKey = QByteArray::fromBase64(folderUserObject.value("encryptedMetadataKey").toString().toUtf8());
 
             if (!encryptedMetadataKey.isEmpty()) {
-                const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey);
+                const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey, certificate.digest(QCryptographicHash::Sha256));
                 if (decryptedMetadataKey.isEmpty()) {
                     break;
                 }
@@ -371,10 +371,11 @@ private slots:
             }
 
             const auto certificatePem = folderUserObject.value("certificate").toString().toUtf8();
+            const auto certificate = QSslCertificate{certificatePem};
             const auto encryptedMetadataKey = QByteArray::fromBase64(folderUserObject.value("encryptedMetadataKey").toString().toUtf8());
 
             if (!encryptedMetadataKey.isEmpty()) {
-                const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey);
+                const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey, certificate.digest(QCryptographicHash::Sha256));
                 if (decryptedMetadataKey.isEmpty()) {
                     break;
                 }
index a46871660d97d9815cef96c02a562b09ab307b51..a5fba860d20af1f80cf7825daa6371cff218930e 100644 (file)
@@ -80,9 +80,8 @@ private slots:
         QVERIFY(!publicKey.isNull());
         QVERIFY(!privateKey.isEmpty());
 
-        _account->e2e()->_certificate = cert;
-        _account->e2e()->_publicKey = publicKey;
-        _account->e2e()->_privateKey = privateKey;
+        _account->e2e()->setCertificate(cert);
+        _account->e2e()->setPrivateKey(privateKey);
 
         QScopedPointer<FolderMetadata> metadata(new FolderMetadata(_account, "/",  FolderMetadata::FolderType::Root));
         QSignalSpy metadataSetupCompleteSpy(metadata.data(), &FolderMetadata::setupComplete);